diff --git a/roles/base/defaults/main.yml b/roles/base/defaults/main.yml index fe0e8fd..0748c9f 100644 --- a/roles/base/defaults/main.yml +++ b/roles/base/defaults/main.yml @@ -17,3 +17,14 @@ evomaintenance_from: "evomaintenance@{{ evomaintenance_from_domain }}" evomaintenance_full_from: "Evomaintenance <{{ evomaintenance_from }}>" evomaintenance_urgency_from: mama.doe@example.com evomaintenance_urgency_tel: "06.00.00.00.00" +evomaintenance_install_vendor: False +evomaintenance_force_config: True +evomaintenance_api_endpoint: Null +evomaintenance_api_key: Null +evomaintenance_hook_api: True +evomaintenance_hook_db: False +evomaintenance_hook_commit: True +evomaintenance_hook_mail: True +evomaintenance_default_hosts: [] +evomaintenance_additional_hosts: [] +evomaintenance_hosts: "{{ evomaintenance_default_hosts | union(evomaintenance_additional_hosts) | unique }}" diff --git a/roles/base/files/evomaintenance.sh b/roles/base/files/evomaintenance.sh index d2a7f52..4820f49 100644 --- a/roles/base/files/evomaintenance.sh +++ b/roles/base/files/evomaintenance.sh @@ -4,82 +4,445 @@ # Dependencies (all OS): git postgresql-client # Dependencies (Debian): sudo -# version 0.4.1 -# Copyright 2007-2018 Evolix +# version 0.5.1 +# Copyright 2007-2019 Evolix , Gregory Colpart , +# Jérémy Lecour and others. + +VERSION="0.5.1" + +show_version() { + cat <, Gregory Colpart , + Jérémy Lecour and others. + +evomaintenance comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU General Public Licence for details. +END +} + +show_help() { + cat <> "${LOGFILE}" + fi + fi + fi +} + +hook_db() { + SQL_DETAILS=$(echo "${MESSAGE}" | sed "s/'/''/g") + PG_QUERY="INSERT INTO evomaint(hostname,userid,ipaddress,begin_date,end_date,details) VALUES ('${HOSTNAME}','${USER}','${IP}','${BEGIN_DATE}',now(),'${SQL_DETAILS}')" + + if [ "${VERBOSE}" = "1" ]; then + printf "\n********** DB query **************\n%s\n***********************************\n" "${PG_QUERY}" + fi + if [ "${DRY_RUN}" != "1" ] && [ -x "${PSQL_BIN}" ]; then + echo "${PG_QUERY}" | ${PSQL_BIN} "${PGDB}" "${PGTABLE}" -h "${PGHOST}" + fi +} + +hook_api() { + if [ "${VERBOSE}" = "1" ]; then + printf "\n********** API call **************\n" + printf "curl -f -s -S -X POST [REDACTED] -k -F api_key=[REDACTED] -F action=insertEvoMaintenance -F hostname=%s -F userid=%s -F ipaddress=%s -F begin_date=%s -F end_date='now()' -F details=%s" \ + "${HOSTNAME}" "${USER}" "${IP}" "${BEGIN_DATE}" "${MESSAGE}" + printf "\n***********************************\n" + fi + + if [ "${DRY_RUN}" != "1" ] && [ -x "${CURL_BIN}" ]; then + API_RETURN_STATUS=$(curl -f -s -S -X POST \ + "${API_ENDPOINT}" -k \ + -F api_key="${API_KEY}" \ + -F action=insertEvoMaintenance \ + -F hostname="${HOSTNAME}" \ + -F userid="${USER}" \ + -F ipaddress="${IP}" \ + -F begin_date="${BEGIN_DATE}" \ + -F end_date='now()' \ + -F details="${MESSAGE}") + + # either cURL or the API backend can throw an error, otherwise it returns this JSON response + if [ "$API_RETURN_STATUS" = '{"status":"Ok"}' ]; then + echo "API call OK." + else + echo "API call FAILED." + fi + fi +} + +format_mail() { + cat <> "${LOGFILE}" + fi +} + +# load configuration if present. test -f /etc/evomaintenance.cf && . /etc/evomaintenance.cf -[ -n "${HOSTNAME}" ] || HOSTNAME=$(get_fqdn) -[ -n "${EVOMAINTMAIL}" ] || EVOMAINTMAIL=evomaintenance-$(echo "${HOSTNAME}" | cut -d- -f1)@${REALM} -[ -n "${LOGFILE}" ] || LOGFILE=/var/log/evomaintenance.log +HOSTNAME=${HOSTNAME:-$(get_fqdn)} +EVOMAINTMAIL=${EVOMAINTMAIL:-"evomaintenance-$(echo "${HOSTNAME}" | cut -d- -f1)@${REALM}"} +LOGFILE=${LOGFILE:-"/var/log/evomaintenance.log"} +HOOK_COMMIT=${HOOK_COMMIT:-"1"} +HOOK_DB=${HOOK_DB:-"0"} +HOOK_API=${HOOK_API:-"1"} +HOOK_MAIL=${HOOK_MAIL:-"1"} +DRY_RUN=${DRY_RUN:-"0"} +VERBOSE=${VERBOSE:-"0"} +AUTO=${AUTO:-"0"} +EVOCHECK=${EVOCHECK:-"0"} +GIT_STATUS_MAX_LINES=${GIT_STATUS_MAX_LINES:-20} +API_ENDPOINT=${API_ENDPOINT:-""} + +# initialize variables +MESSAGE="" +# GIT_COMMITS_SHORT="" +GIT_COMMITS="" + +# Parse options +# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a +while :; do + case $1 in + -h|-\?|--help) + show_help + exit 0 + ;; + --version) + show_version + exit 0 + ;; + -m|--message) + # message options, with value speparated by space + if [ -n "$2" ]; then + MESSAGE=$2 + shift + else + printf 'ERROR: "--message" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --message=?*) + # message options, with value speparated by = + MESSAGE=${1#*=} + ;; + --message=) + # message options, without value + printf 'ERROR: "--message" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + --no-commit) + # disable commit hook + HOOK_COMMIT=0 + ;; + --commit) + # enable commit hook + HOOK_COMMIT=1 + ;; + --no-db) + # disable DB hook + HOOK_DB=0 + ;; + --db) + # enable DB hook + HOOK_DB=1 + ;; + --no-api) + # disable API hook + HOOK_API=0 + ;; + --api) + # enable API hook + HOOK_API=1 + ;; + --no-mail) + # disable mail hook + HOOK_MAIL=0 + ;; + --mail) + # enable mail hook + HOOK_MAIL=1 + ;; + --no-auto) + # use "manual" mode + AUTO=0 + ;; + --auto) + # use "auto" mode + AUTO=1 + ;; + -n|--dry-run) + # disable actual commands + DRY_RUN=1 + ;; + -v|--verbose) + # print verbose information + VERBOSE=1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + # Treat unset variables as an error when substituting. # Only after this line, because some config variables might be missing. set -u -REAL_HOSTNAME=$(get_fqdn) -if [ "${HOSTNAME}" = "${REAL_HOSTNAME}" ]; then - HOSTNAME_TEXT="${HOSTNAME}" -else - HOSTNAME_TEXT="${HOSTNAME} (${REAL_HOSTNAME})" -fi - +# Gather information +HOSTNAME_TEXT=$(get_complete_hostname) # TTY=$(get_tty) # WHO=$(get_who) IP=$(get_ip) @@ -90,109 +453,278 @@ USER=$(logname) PATH=${PATH}:/usr/sbin SENDMAIL_BIN=$(command -v sendmail) +readonly SENDMAIL_BIN +if [ "${HOOK_MAIL}" = "1" ] && [ -z "${SENDMAIL_BIN}" ]; then + echo "No \`sendmail' command has been found, can't send mail." 2>&1 +fi + GIT_BIN=$(command -v git) +readonly GIT_BIN +if [ "${HOOK_COMMIT}" = "1" ] && [ -z "${GIT_BIN}" ]; then + echo "No \`git' command has been found, can't commit changes" 2>&1 +fi + +PSQL_BIN=$(command -v psql) +readonly PSQL_BIN +if [ "${HOOK_DB}" = "1" ] && [ -z "${PSQL_BIN}" ]; then + echo "No \`psql' command has been found, can't save to the database." 2>&1 +fi + +CURL_BIN=$(command -v curl) +readonly CURL_BIN +if [ "${HOOK_API}" = "1" ] && [ -z "${CURL_BIN}" ]; then + echo "No \`curl' command has been found, can't call the API." 2>&1 +fi + +if [ "${HOOK_API}" = "1" ] && [ -z "${API_ENDPOINT}" ]; then + echo "No API endpoint specified, can't call the API." 2>&1 +fi + +EVOCHECK_BIN="/usr/share/scripts/evocheck.sh" GIT_REPOSITORIES="/etc /etc/bind" -# git statuses +# initialize variable GIT_STATUSES="" - -if test -x "${GIT_BIN}"; then +# git statuses +if [ -x "${GIT_BIN}" ]; then # loop on possible directories managed by GIT for dir in ${GIT_REPOSITORIES}; do - # tell Git where to find the repository and the work tree (no need to `cd …` there) - export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}" - # If the repository and the work tree exist, try to commit changes - if test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then - CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ') - if [ "${CHANGED_LINES}" != "0" ]; then - STATUS=$(${GIT_BIN} status --short | tail -n 10) - # append diff data, without empty lines - GIT_STATUSES=$(printf "%s\n%s\n%s\n" "${GIT_STATUSES}" "${GIT_DIR} (last 10 lines)" "${STATUS}" | sed -e '/^$/d') - fi + RESULT=$(get_repository_status "${dir}") + if [ -n "${RESULT}" ]; then + # append diff data, without empty lines + GIT_STATUSES=$(printf "%s\n%s\n" "${GIT_STATUSES}" "${RESULT}" | sed -e '/^$/d') fi - # unset environment variables to prevent accidental influence on other git commands - unset GIT_DIR GIT_WORK_TREE + unset RESULT done - if [ -n "${GIT_STATUSES}" ]; then - echo "/!\ There are some uncommited changes. If you proceed, everything will be commited." - echo "${GIT_STATUSES}" - echo "" - fi fi -# get input from stdin -echo "> Please, enter details about your maintenance" -read TEXTE +# find out if running in interactive mode, or not +if [ -t 0 ]; then + INTERACTIVE=1 +else + INTERACTIVE=0 +fi +readonly INTERACTIVE -if [ "${TEXTE}" = "" ]; then +if [ "${INTERACTIVE}" = "1" ] && [ "${EVOCHECK}" = "1" ]; then + get_evocheck +fi +if [ -n "${GIT_STATUSES}" ] && [ "${INTERACTIVE}" = "1" ]; then + printf "/!\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}" +fi + +if [ -z "${MESSAGE}" ]; then + if [ "${INTERACTIVE}" = "1" ]; then + printf "> Please, enter details about your maintenance:\n" + fi + read -r MESSAGE +fi + +if [ -z "${MESSAGE}" ]; then echo "no value..." exit 1 fi -# recapitulatif -BLOB=$(cat < Press to submit, or to cancel." -read enter - -# write log -echo "----------- $(get_now) ---------------" >> "${LOGFILE}" -echo "${BLOB}" >> "${LOGFILE}" - -# git commit -GIT_COMMITS="" - -if test -x "${GIT_BIN}"; then - # loop on possible directories managed by GIT - for dir in ${GIT_REPOSITORIES}; do - # tell Git where to find the repository and the work tree (no need to `cd …` there) - export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}" - # If the repository and the work tree exist, try to commit changes - if test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then - CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ') - if [ "${CHANGED_LINES}" != "0" ]; then - ${GIT_BIN} add --all - ${GIT_BIN} commit --message "${TEXTE}" --author="${USER} <${USER}@evolix.net>" --quiet - # Add the SHA to the log file if something has been committed - SHA=$(${GIT_BIN} rev-parse --short HEAD) - STATS=$(${GIT_BIN} show --stat | tail -1) - # append commit data, without empty lines - GIT_COMMITS=$(printf "%s\n%s : %s –%s" "${GIT_COMMITS}" "${GIT_DIR}" "${SHA}" "${STATS}" | sed -e '/^$/d') - fi +if [ "${INTERACTIVE}" = "1" ] && [ "${AUTO}" = "0" ]; then + if [ "${HOOK_COMMIT}" = "1" ] || [ "${HOOK_MAIL}" = "1" ] || [ "${HOOK_DB}" = "1" ]; then + printf "\nActions to execute:\n" + if [ "${HOOK_COMMIT}" = "1" ]; then + printf "* commit changes in repositories\n" fi - # unset environment variables to prevent accidental influence on other git commands - unset GIT_DIR GIT_WORK_TREE - done - if [ -n "${GIT_COMMITS}" ]; then - echo "${GIT_COMMITS}" >> "${LOGFILE}" + if [ "${HOOK_MAIL}" = "1" ]; then + printf "* send mail to %s\n" "${EVOMAINTMAIL}" + fi + if [ "${HOOK_DB}" = "1" ]; then + printf "* save metadata to the database\n" + fi + if [ "${HOOK_API}" = "1" ]; then + printf "* send metadata to the API\n" + fi + echo "" + + answer="" + while :; do + printf "> Let's continue? [Y,n,i,?] " + read -r answer + case $answer in + [Yy]|"" ) + # force "auto" mode, but keep hooks settings + AUTO=1 + break + ;; + [Nn] ) + # force "auto" mode, and disable all hooks + HOOK_COMMIT=0 + HOOK_MAIL=0 + HOOK_DB=0 + HOOK_API=0 + AUTO=1 + break + ;; + [Ii] ) + # force "manual" mode + AUTO=0 + break + ;; + * ) + printf "y - yes, execute actions and exit\n" + printf "n - no, don't execute actions and exit\n" + printf "i - switch to interactive mode\n" + printf "? - print this help\n" + ;; + esac + done fi fi -# insert into PG -# SQL_TEXTE=`echo "${TEXTE}" | sed "s/'/\\\\\\'/g ; s@/@\\\\\/@g ; s@\\&@et@g"` -SQL_TEXTE=`echo "${TEXTE}" | sed "s/'/''/g"` +if [ "${INTERACTIVE}" = "1" ] && [ "${AUTO}" = "0" ]; then + # Commit hook + if [ -n "${GIT_STATUSES}" ] && [ "${HOOK_COMMIT}" = "1" ]; then + printf "/!\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}" -PG_QUERY="INSERT INTO evomaint(hostname,userid,ipaddress,begin_date,end_date,details) VALUES ('${HOSTNAME}','${USER}','${IP}','${BEGIN_DATE}',now(),'${SQL_TEXTE}')" -echo "${PG_QUERY}" | psql ${PGDB} ${PGTABLE} -h ${PGHOST} --quiet + y="Y"; n="n" + answer="" + while :; do + printf "> Do you want to commit the changes? [%s] " "${y},${n}" + read -r answer + case $answer in + [Yy] ) + hook_commit; + break + ;; + [Nn] ) + break + ;; + "" ) + if [ "${HOOK_COMMIT}" = "1" ]; then + hook_commit + fi + break + ;; + * ) + echo "answer with a valid choice" + ;; + esac + done + fi -# send mail -MAIL_TEXTE=$(echo "${TEXTE}" | sed -e "s@/@\\\\\/@g ; s@&@\\\\&@") -MAIL_GIT_COMMITS=$(echo "${GIT_COMMITS}" | sed -e "s@/@\\\\\/@g ; s@&@\\\\&@") + # Mail hook + if [ "${HOOK_MAIL}" = "1" ]; then + y="Y"; n="n" + else + y="y"; n="N" + fi + answer="" + while :; do + printf "> Do you want to send an email to <%s>? [%s] " "${EVOMAINTMAIL}" "${y},${n},e" + read -r answer + case $answer in + [Yy] ) + hook_mail; + break + ;; + [Nn] ) + break + ;; + [Ee] ) + printf "> To: [%s] " "${EVOMAINTMAIL}" + read -r mail_recipient + if [ -n "${mail_recipient}" ]; then + EVOMAINTMAIL="${mail_recipient}" + fi + ;; + "" ) + if [ "${HOOK_MAIL}" = "1" ]; then + hook_mail + fi + break + ;; + * ) + echo "answer with a valid choice" + ;; + esac + done -cat /usr/share/scripts/evomaintenance.tpl | \ - sed -e "s/__TO__/${EVOMAINTMAIL}/ ; s/__HOSTNAME__/${HOSTNAME_TEXT}/ ; s/__USER__/${USER}/ ; s/__BEGIN_DATE__/${BEGIN_DATE}/ ; s/__END_DATE__/${END_DATE}/ ; s/__GIT_COMMITS__/${MAIL_GIT_COMMITS}/ ; s/__TEXTE__/${MAIL_TEXTE}/ ; s/__IP__/${IP}/ ; s/__FULLFROM__/${FULLFROM}/ ; s/__FROM__/${FROM}/ ; s/__URGENCYFROM__/${URGENCYFROM}/ ; s/__URGENCYTEL__/${URGENCYTEL}/" | \ - ${SENDMAIL_BIN} -oi -t -f ${FROM} + # Database hook + if [ "${HOOK_DB}" = "1" ]; then + y="Y"; n="n" + else + y="y"; n="N" + fi + answer="" + while :; do + printf "> Do you want to insert your message into the database? [%s] " "${y},${n}" + read -r answer + case $answer in + [Yy] ) + hook_db; + break + ;; + [Nn] ) + break + ;; + "" ) + if [ "${HOOK_DB}" = "1" ]; then + hook_db + fi + break + ;; + * ) + echo "answer with a valid choice" + ;; + esac + done + + # API hook + if [ "${HOOK_API}" = "1" ]; then + y="Y"; n="n" + else + y="y"; n="N" + fi + answer="" + while :; do + printf "> Do you want to send the metadata to the API? [%s] " "${y},${n}" + read -r answer + case $answer in + [Yy] ) + hook_api; + break + ;; + [Nn] ) + break + ;; + "" ) + if [ "${HOOK_API}" = "1" ]; then + hook_api + fi + break + ;; + * ) + echo "answer with a valid choice" + ;; + esac + done +fi + +# Log hook +hook_log + +if [ "${INTERACTIVE}" = "0" ] || [ "${AUTO}" = "1" ]; then + if [ "${HOOK_COMMIT}" = "1" ]; then + hook_commit + fi + if [ "${HOOK_MAIL}" = "1" ]; then + hook_mail + fi + if [ "${HOOK_DB}" = "1" ]; then + hook_db + fi + if [ "${HOOK_API}" = "1" ]; then + hook_api + fi +fi exit 0 diff --git a/roles/base/templates/evomaintenance.j2 b/roles/base/templates/evomaintenance.j2 index 79bc0cb..006d1c0 100644 --- a/roles/base/templates/evomaintenance.j2 +++ b/roles/base/templates/evomaintenance.j2 @@ -1,13 +1,20 @@ HOSTNAME={{ evomaintenance_hostname }} EVOMAINTMAIL={{ evomaintenance_alert_email or general_alert_email | mandatory }} -export PGPASSWORD={{ evomaintenance_pg_passwd | mandatory }} +export PGPASSWORD={{ evomaintenance_pg_passwd }} -PGDB={{ evomaintenance_pg_db | mandatory }} -PGTABLE={{ evomaintenance_pg_table | mandatory }} -PGHOST={{ evomaintenance_pg_host | mandatory }} +PGDB={{ evomaintenance_pg_db }} +PGTABLE={{ evomaintenance_pg_table }} +PGHOST={{ evomaintenance_pg_host }} FROM={{ evomaintenance_from }} FULLFROM="{{ evomaintenance_full_from }}" URGENCYFROM={{ evomaintenance_urgency_from }} URGENCYTEL="{{ evomaintenance_urgency_tel }}" REALM="{{ evomaintenance_realm }}" +API_ENDPOINT={{ evomaintenance_api_endpoint }} +API_KEY={{ evomaintenance_api_key }} + +HOOK_API={{ evomaintenance_hook_api | bool | ternary('1','0') }} +HOOK_DB={{ evomaintenance_hook_db | bool | ternary('1','0') }} +HOOK_COMMIT={{ evomaintenance_hook_commit | bool | ternary('1','0') }} +HOOK_MAIL={{ evomaintenance_hook_mail | bool | ternary('1','0') }}