#!/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 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 LOG_LEVEL=$LOG_LEVEL_DEBUG log() { local log_type="$1" # 'debug', 'info', 'warning' or 'error' local message="$2" local message_color='' local message_level='' case "$log_type" in 'error') message_color="$COLOR_RED" message_level="$LOG_LEVEL_ERROR" ;; 'warning') message_color="$COLOR_YELLOW" message_level="$LOG_LEVEL_WARNING" ;; 'info') message_color="$COLOR_BLUE" message_level="$LOG_LEVEL_INFO" ;; 'debug') message_color="$COLOR_PURPLE" message_level="$LOG_LEVEL_DEBUG" ;; *) echo "unexpected log type $log_type" exit "$RETURNCODE_ERROR" esac if [ "$message_level" -le "$LOG_LEVEL" ] then printf "%b : %s\n" "${message_color}${log_type}${COLOR_RESET}" "$message" fi } 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 } 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}" } 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" 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 } 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 } deboco__init() { local debops_controller_path="$1" debops_controller_path=$(realpath $debops_controller_path) 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 local exit_code='' 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" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi 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 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) 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 replace_in_file "$debops_update_script_path" \ 'virtualenv --quiet -- ' \ "${PYTHON_PATH} -m venv " if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi fi 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" 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 controller__check_integrity "$debops_controller_path" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi } deboco__update() { local debops_controller_path="$1" debops_controller_path=$(realpath $debops_controller_path) log 'info' "updating debobs controller in $debops_controller_path" controller__check_integrity "$debops_controller_path" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi local exit_code='' 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} 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 controller__check_integrity "$debops_controller_path" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi } 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 debops_controller_path=$(realpath $debops_controller_path) 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 debops_controller_path=$(realpath $debops_controller_path) log 'info' "installing debops bootstrap on $target_host_fqdn using debobs controller $debops_controller_path" controller__check_integrity "$debops_controller_path" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi 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") local debops_exit_code='' 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} 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" ] 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" local target_host_fqdn="$2" # eg alambix-108.ipr.univ-rennes.fr debops_controller_path=$(realpath $debops_controller_path) log 'info' "updating $target_host_fqdn using debobs controller $debops_controller_path" controller__check_integrity "$debops_controller_path" if [ $? != "$RETURNCODE_SUCCESS" ] then return $RETURNCODE_ERROR fi 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)-update-${target_host_fqdn} echo "applying debops configuration 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") local debops_exit_code='' log 'debug' "local_repos_path=$local_repos_path" 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} 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" ] 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:" echo " $0 [options] " echo echo "where:" echo " [options] is a set of:" echo " --python-path " echo " specifies the python executable to use (must be python 3.10 or above). Default is $DEFAULT_PYTHON_PATH" echo echo " is one of:" echo " init " echo " creates a debops controller in " echo echo " update " echo " updates the debops controller in " echo echo " init_machine " echo " installs debops bootstrap on the machine using the debops controller in " echo echo " update_machine " echo " updates the machine using the debops controller in " echo echo " configure_machine " echo " configures the machine using the debops controller in ". Equivivalent to a init_machine followed by update_machine echo echo "example:" echo " deboco --python-path /usr/bin/python3.10 init ~/work/debops/constrollers/alambix" } deboco() { 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 } deboco $@