#!/bin/bash # Purpose: detect modified files in config tree and execute # any defined service modifications ################################################################################ set -e set -E set -u # support testsuite FUNCTIONS="${FUNCTIONS:-/usr/share/ngcp-ngcpcfg/functions/}" OUTPUT_DIRECTORY="${OUTPUT_DIRECTORY:-}" CLEANUP_FILES=() INSTANCES=() # load modules if [[ ! -r "${FUNCTIONS}"/main ]]; then printf "Error: %s/main could not be read. Exiting.\n" "${FUNCTIONS}">&2 exit 1 fi # shellcheck disable=SC1090 . "${FUNCTIONS}"/main # functions instances_info() { local target local dest local instance local RUNNER="nice -n 19 ionice -c 3" log_debug "${RUNNER} ${HELPER}/instances-info" while IFS=":" read -r -a info; do target=${info[0]} dest=${info[1]} instance=${info[2]} INSTANCES+=("${target}:${instance}:${dest}") done< <(${RUNNER} "${HELPER}/instances-info") } exec_wrapper() { local service_file=$1 local instance_name="${2:-}" local msg="" if [ -n "${instance_name}" ]; then msg="[${instance_name}]" fi if ${DRYRUN} ; then log_info "TEST MODE: Would execute action for ${service_file}${msg}" return 0 fi log_info "Executing action for ${service_file}${msg}" if [[ -x "${service_file}" ]]; then log_debug "${service_file}${msg}" if ! INSTANCE_NAME="${instance_name}" "${service_file}" ; then log_warn "INSTANCE_NAME=${instance_name} ${service_file} returned with error code, continuing anyway." fi elif [[ -r "${service_file}" ]]; then log_debug "INSTANCE_NAME=${instance_name} bash ${service_file}" if ! INSTANCE_NAME="${instance_name}" bash "${service_file}" ; then log_warn "INSTANCE_NAME=${instance_name} ${service_file} returned with error code, continuing anyway." fi else log_error "Error: ${service_file} could not be read." exit 1 fi } # get rid of "./" and "//" in file names normalize_files() { NORMALIZED_FILES="$(mktemp -t ngcpcfg-services-norm.XXXXXXXXXX)" log_debug "NORMALIZED_FILES = ${NORMALIZED_FILES}" CLEANUP_FILES+=("${NORMALIZED_FILES}") while read -r line ; do # shellcheck disable=SC2001 echo "${line}" | sed -e 's_\./_/_g ; s_//_/_g' >> "${NORMALIZED_FILES}" done < "${TMPFILE}" } # restart sysctl services before the rest (see TT#58703) # restart monit services before the rest (see MT#9971) # restart HA just after monit (see MT#17163) # restart other services in alphabetical order sort_service_list() { SORTED_LIST="$(mktemp -t ngcpcfg-services-sorted.XXXXXXXXXX)" log_debug "SORTED_LIST = ${SORTED_LIST}" CLEANUP_FILES+=("${SORTED_LIST}") for dir in sysctl.d monit corosync pacemaker ha.d; do grep "${SERVICES_POOL_BASE}"/etc/${dir}/'.*services' \ "${NORMALIZED_FILES}" >> "${SORTED_LIST}" || true done # Sort loads the entire contents before outputting, and we are appending # instead of truncating, so this should be safe. # shellcheck disable=SC2094 sort "${NORMALIZED_FILES}" "${SORTED_LIST}" | uniq -u \ >> "${SORTED_LIST}" || true } services_start_queue() { log_info "Clearing out enqueued services actions" ngcp-service queue-clear log_info "Starting enqueued services actions mode" ngcp-service queue-start } execute() { local line local info while read -r line ; do while IFS=':' read -ra info; do exec_wrapper "${info[0]}" "${info[1]:-}" done <<< "${line}" done < "${SORTED_LIST}" } services_flush_enqueued() { log_info "Executing enqueued services actions" ngcp-service --queue=default,policy-rc.d queue-show ngcp-service --queue=default,policy-rc.d queue-run } services_sync_state() { log_info "Synchronizing current with expected services state" ngcp-service sync-state } systemd_daemon_reload_preset() { if [[ -d "/run/systemd/system" ]]; then log_info "Reloading systemd daemon and preset all services" log_debug "systemd needs daemon-reload so the unit files loaded are in sync" log_debug "Running: systemctl daemon-reload 2>&1 || true" systemctl daemon-reload 2>&1 || true log_debug "Removing any broken systemd service symlink" find -L /etc/systemd/system -type l \ | xargs -r rm log_debug "systemd needs preset-all to enable/disable services (to start them on boot)" log_debug "Running: rm -rf /etc/systemd/system/*.wants/ || true" rm -rf /etc/systemd/system/*.wants/ || true log_debug "Running: systemctl preset-all 2>&1 || true" systemctl preset-all 2>&1 || true fi } is_absolute_path() { local dir="$1" if [[ ! "${dir}" =~ ^/ ]]; then log_error "${dir} is not an absolute path" return 1 fi return 0 } is_non_git_folder() { local dir="$1" if [[ ! -d "${dir}/.git" ]]; then log_info "${dir} has no support of .services" return 1 fi return 0 } generate_list_to_process() { local dir="$1" if ${FORCE_ALL_SERVICES} ; then log_debug "calling function find_all_services()" find_all_services "${dir}" else log_debug "calling function find_all_changed_services()" find_all_changed_services "${dir}" fi } find_instance() { local service_file local info local target # get rid of "./" and "//" # shellcheck disable=SC2001 service_file=$(echo "$1" | sed -e 's_\./_/_g ; s_//_/_g') for instance in "${INSTANCES[@]}"; do IFS=":" read -ra info <<< "${instance}" # get rid of "./" and "//" target=$(echo "${TEMPLATE_POOL_BASE}${info[0]}" | sed -e 's_\./_/_g ; s_//_/_g') if [[ "${service_file}" =~ ^${target} ]] ; then log_debug "[${info[1]}] Storing ${file} in '${TMPFILE}'" echo "${file}:${info[1]}" >> "${TMPFILE}" fi done } find_all_services() { local dir="$1" while read -r file ; do if [[ ! -r "${file}" ]]; then log_warn "Cannot read file '${file}'" fi log_debug "Storing ${file} in '${TMPFILE}'" echo "${file}" >> "${TMPFILE}" find_instance "${file}" done < <(find "${SERVICES_POOL_BASE}/${dir}" -name '*.services' | sort -u) } find_changed_instance_services() { local file local dir local info local dest local target local target_file # get rid of "./" and "//" # shellcheck disable=SC2001 file=$(echo "$1"| sed -e 's_\./_/_g ; s_//_/_g') # shellcheck disable=SC2001 dir=$(echo "$2"| sed -e 's_\./_/_g ; s_//_/_g') for instance in "${INSTANCES[@]}"; do IFS=":" read -ra info <<< "${instance}" target=${info[0]#${dir}/} dest=${info[2]#${dir}/} if [[ "${file}" =~ ^${dest} ]] ; then target_file=${file/${dest}/${target}} if [[ -r "${SERVICES_POOL_BASE}/${dir}/${target_file}".services ]]; then log_debug "[${info[1]}] Storing ${SERVICES_POOL_BASE}/${dir}/${target_file}.services in '${TMPFILE}'" echo "${SERVICES_POOL_BASE}/${dir}/${target_file}.services:${info[1]}" elif [[ -r "${SERVICES_POOL_BASE}/${dir}/$(dirname "${target_file}")"/ngcpcfg.services ]]; then log_debug "[${info[1]}] Storing ${SERVICES_POOL_BASE}/${dir}/$(dirname "${target_file}")/ngcpcfg.services in '${TMPFILE}'" echo "${SERVICES_POOL_BASE}/${dir}/$(dirname "${target_file}")/ngcpcfg.services:${info[1]}" fi fi done } find_all_changed_services() { local dir="$1" log_debug "${FUNCNAME[0]}(): Working in ${OUTPUT_DIRECTORY}${dir}" pushd "${OUTPUT_DIRECTORY}${dir}" >/dev/null for file in $(git status -uall --porcelain | sed 's/^...//') ; do if ! [[ -r "${file}" ]]; then continue elif [[ -r "${SERVICES_POOL_BASE}/${dir}/${file}".services ]]; then log_debug "Storing ${SERVICES_POOL_BASE}/${dir}/${file}.services in '${TMPFILE}'" echo "${SERVICES_POOL_BASE}/${dir}/${file}".services elif [[ -r "${SERVICES_POOL_BASE}/${dir}/$(dirname "${file}")"/ngcpcfg.services ]]; then log_debug "Storing ${SERVICES_POOL_BASE}/${dir}/$(dirname "$file")/ngcpcfg.services in '${TMPFILE}'" echo "${SERVICES_POOL_BASE}/${dir}/$(dirname "$file")/ngcpcfg.services" else find_changed_instance_services "${file}" "${dir}" fi done | sort -u >> "${TMPFILE}" popd >/dev/null } # main script RUNNING_FILE="${RUN_DIR}/ngcpcfg-services.running" DRYRUN='false' FORCE_ALL_SERVICES='false' while [ -n "${1:-}" ]; do case "$1" in test|--dry-run) DRYRUN='true' shift ;; --force-all-services) FORCE_ALL_SERVICES='true' shift ;; *) break ;; esac done log_debug "DRYRUN = ${DRYRUN}" log_debug "FORCE_ALL_SERVICES = ${FORCE_ALL_SERVICES}" if ! ${DRYRUN}; then log_debug "creating current execution filesystem trail" trap 'rm -f "${RUNNING_FILE}"' EXIT ERR echo $$ >"${RUNNING_FILE}" fi log_debug "systemd_daemon_reload_preset function" systemd_daemon_reload_preset TMPFILE="$(mktemp -t ngcpcfg-services-tmp.XXXXXXXXXX)" log_debug "TMPFILE = ${TMPFILE}" log_debug "OUTPUT_DIRECTORY=${OUTPUT_DIRECTORY}" CLEANUP_FILES+=("${TMPFILE}") if [ -n "${TEMPLATE_INSTANCES:-}" ] && [ -f "${TEMPLATE_INSTANCES}" ] ; then instances_info fi for dir in ${CONFIG_POOL} ; do is_absolute_path "${OUTPUT_DIRECTORY}${dir}" || continue is_non_git_folder "${OUTPUT_DIRECTORY}${dir}" || continue generate_list_to_process "${dir}" done if [[ -s "${TMPFILE}" ]]; then log_debug "normalize_files function" normalize_files log_debug "sort_service_list function" sort_service_list services_start_queue log_debug "execute function" execute else log_debug "No services file(s) reported - no explicit service state changed." fi # Always flush the queues, to handle actions from package upgrades. services_flush_enqueued log_debug "services_sync_state function" services_sync_state if [[ -n "${DEBUG:-}" ]]; then log_debug "Not removing temporary files" else rm -f "${CLEANUP_FILES[@]}" fi ## END OF FILE #################################################################