#!/bin/sh

alterator_api_version=1
po_domain="alterator-mastercontrol"

. zabbix-mastercontrol-sh-functions
. alterator-sh-functions

SSH_KEYDIR="/root/.ssh"
DISPATCH_PIDFILE=/var/run/mastercontrol-dispatch.pid
DISPATCH_PROCESSED=/var/cache/alterator/mastercontrol/processed
DISPATCH_FAILED=/var/cache/alterator/mastercontrol/failed
DISPATCH_LOG=/var/log/mastercontrol-dispatch.log

write_error_message() {
        local msg="$1"; shift

        if [ $# -gt 0 ]; then
                msg="$(printf "$msg\\n" "$@")"
        fi

        write_error "$msg"
}

sshkey_dir() {
        local filename="${1-${in_sshkey-}}"
        local path=
        local dir=
        
        path="${filename#$SSH_KEYDIR/}"
        dir="${path%/*}"

        if [ "$dir" != "$path" ]; then
                echo "$dir"
        fi
}

is_ssh_private_key() {
        local file="${1-${in_sshkey-}}"

        [ -e "$file" ] && head -1 "$file" 2>/dev/null | grep -qP 'BEGIN\s+.+\s+PRIVATE\s+KEY'
}

run_ssh_with_passwd() {
	local passwd="$1"; shift

	expect -f - <<EOF
log_user 0
spawn $@
expect {
        "Enter passphrase*" {
		send "$passwd\\n"
		exp_continue
	} eof {
		send_user "\$expect_out(buffer)"
		catch wait result
		exit [lindex \$result 3]
	}
}
catch wait result
exit [lindex \$result 3]
EOF
}

ssh_public_key_fingerprint() {
        local sshkey="${1-${in_sshkey-}}"
        local res=
        local fingerprint=
        
        res="$(ssh-keygen -l -f "$sshkey")"
        if [ $? -eq 0 ] && [ -n "$res" ]; then
                fingerprint="$(echo "$res" | cut -f2 -d ' ')"
        fi

        [ -n "$fingerprint" ] && echo "$fingerprint"
}

install_ssh_key() {
        local sshkey="${1-${in_sshkey-}}"
        local passwd="${2-${in_passwd-}}"
        local tmpdir="$(mktemp -d)"
        local fingerprint=

        install -D -m0600 "$sshkey" "${tmpdir%/}/id"
        run_ssh_with_passwd "$passwd" ssh-keygen -y -f "${tmpdir%/}/id" >"${tmpdir%/}/id.pub"
        if [ $? -eq 0 ] && [ -s "${tmpdir%/}/id.pub" ]; then
                fingerprint="$(ssh_public_key_fingerprint "${tmpdir%/}/id.pub")"
                if [ $? -eq 0 ]; then
                        write_string_param 'fingerprint' "$fingerprint"
                        install -D -m0600 "${tmpdir%/}/id" "${SSH_KEYDIR%/}/mastercontrol/$fingerprint"
                        install -D -m0600 "${tmpdir%/}/id.pub" "${SSH_KEYDIR%/}/mastercontrol/$fingerprint.pub"
                else
                        write_error_message "`_ 'Unable to calculate the public key fingerprint'`"
                fi
        else
                write_error_message "`_ 'Unable to calculate the public key'`"
        fi

        rm -rf "$tmpdir"
}

read_zabbix_mysql_stats() {
        local head="$1"
        local host="$2"
        local group="$3"
        local filtersql="$4"

        stats_zabbix_mysql_hosts "$head" "$host" "$group" "$filtersql" | _split_tabs | (
                okok=0
                mod=0
                dis=0
                unk=0
                other=0
                total=0
                IFS=$_IFS
                while read -r count vstat lvstat; do
                        if [ "$vstat" = 'OK' ] && [ "$lvstat" = 'OK' ]; then
                                okok=$(($okok + $count))
                        elif [ "$vstat" = 'OK' ] && [ "$lvstat" = 'MODIFIED' ]; then
                                mod=$((mod + $count))
                        elif [ "$vstat" = 'MODIFIED' ]; then
                                mod=$((mod + $count))
                        elif [ "$lvstat" = 'DISABLED' ]; then
                                dis=$(($dis + $count))
                        elif [ "$lvstat" = 'UNKNOWN' ]; then
                                unk=$(($unk + $count))
                        else
                                other=$(($other + $count))
                        fi
                        total=$(($total + $count))
                done

                printf '%i\t%i\t%i\t%i\t%i\t%i\n' "$okok" "$mod" "$dis" "$unk" "$other" "$total"
        )
}

start_dispatch() {
        if ! stop_dispatch; then
                write_error_message "`_ 'Unable to stop the current dispatch process'`"
                return 3
        fi

        if [ -z "$DISPATCH_URL" ]; then
                write_error_message "`_ 'Repository URL is not specified'`"
                return 1
        fi
        if [ -z "$DISPATCH_HEAD" ]; then
                write_error_message "`_ 'Profile name or hash is not specified'`"
                return 2
        fi

        write_processed 0
        write_failed 0
        dispatch &
        echo $! >$DISPATCH_PIDFILE
}

read_processed() {
        local processed=$(cat $DISPATCH_PROCESSED 2>/dev/null)

        if [ -n "$processed" ]; then
                echo "$processed"
        else
                echo "0"
        fi
}

write_processed() {
        local processed=${1-0}
        mkdir -p ${DISPATCH_PROCESSED%/*}
        echo "$processed" >$DISPATCH_PROCESSED
}

read_failed() {
        local failed=$(cat $DISPATCH_FAILED 2>/dev/null)

        if [ -n "$failed" ]; then
                echo "$failed"
        else
                echo "0"
        fi
}

write_failed() {
        local failed=${1-0}
        mkdir -p ${DISPATCH_FAILED%/*}
        echo "$failed" >$DISPATCH_FAILED
}

stop_dispatch() {
        local pid=
        local att=

        if [ -e $DISPATCH_PIDFILE ]; then
                pid="$(cat $DISPATCH_PIDFILE)"
                rm -f $DISPATCH_PIDFILE
        fi

        if [ -n "$pid" ]; then
                if kill -0 $pid 2>/dev/null; then
                        kill $pid 2>/dev/null
                fi
                att=1
                while kill -0 $pid 2>/dev/null && [ $att -lt 10 ]; do
                        sleep 1
                        kill $pid 2>/dev/null
                        $att=$(($att + 1))
                done
                if kill -0 $pid 2>/dev/null; then
                        kill -9 $pid 2>/dev/null
                fi

                if kill -0 $pid 2>/dev/null; then
                        return 1
                else
                        if pkill -0 -P $pid 2>/dev/null; then
                                pkill -P $pid 2>/dev/null
                        fi
                        att=1
                        while pkill -0 -P $pid 2>/dev/null && [ $att -lt 10 ]; do
                                sleep 1
                                pkill -P $pid 2>/dev/null
                                $att=$(($att + 1))
                        done
                        if pkill -0 -P $pid 2>/dev/null; then
                                pkill -9 -P $pid 2>/dev/null
                        fi

                        if pkill -0 -P $pid 2>/dev/null; then
                                return 2
                        fi
                fi
        fi
        
        write_processed 0
        write_failed 0

        return 0
}

dispatch() {
        local processed=$(read_processed)
        local failed=$(read_failed)
        local total=$(read_${DISPATCH_SOURCE}_stats "$DISPATCH_HEAD" "$DISPATCH_HOST" "$DISPATCH_GROUP" "$DISPATCH_FILTERSQL" | sed -e 's/^.*[[:space:]]\+//')
        local limit=100
        local timeout=5

        rm -f $DISPATCH_LOG
        touch $DISPATCH_LOG
        while [ $(read_processed) -lt $total ]; do
                processed=$(read_processed)
                failed=$(read_failed)
                list_${DISPATCH_SOURCE}_hosts "$DISPATCH_HOST" "$DISPATCH_GROUP" "$DISPATCH_FILTERSQL" "$processed" "$limit" | (
                        IFS=$_IFS
                        _split_tabs | \
                                while read -r hostid hostname addr profile current; do
                                        cmd="ssh -v -i \"$DISPATCH_SSHKEY\" -o \"ConnectTimeout $timeout\" -o \"CheckHostIP no\" -o \"StrictHostKeyChecking no\" -o \"PasswordAuthentication no\" root@$addr -- etcgit switch \"$DISPATCH_URL\" \"$DISPATCH_HEAD\""
                                        echo "[$addr] $cmd" >>$DISPATCH_LOG
                                        IFS=
                                        run_ssh_with_passwd "$DISPATCH_PASSWD" "$cmd" >>$DISPATCH_LOG 2>&1
                                        ret=$?

                                        if [ $ret -eq 0 ]; then
                                                ${DISPATCH_SOURCE}_set_profile_hash "$hostid" "$DISPATCH_HEAD"
                                                ret=$?
                                                if [ $ret -ne 0 ]; then
                                                        echo "Error updating the host etcgit macro" >>$DISPATCH_LOG
                                                fi
                                        fi
                                        if [ $ret -eq 0 ]; then
                                                echo "[$addr] OK" >>$DISPATCH_LOG
                                        else
                                                failed=$(($failed + 1))
                                                write_failed $failed
                                                echo "[$addr] FAILED" >>$DISPATCH_LOG
                                        fi
                                        processed=$(($processed + 1))
                                        write_processed $processed
                                        IFS=$_IFS
                                done
                )
        done

        rm -f $DISPATCH_PIDFILE
}

on_message()
{
        [ "${in_host-}" = '#f' ] && in_host=
        [ "${in_group-}" = '#f' ] && in_group=
        [ "${in_group-}" = 'ALL' ] && in_group=
        [ "${in_filter-}" = '#f' ] && in_filter=
        [ "${in_filtersql-}" = '#f' ] && in_filtersql=
        [ "${in_hoststatus-}" = '#f' ] && in_hoststatus=
        [ "${in_hoststatus-}" = 'any' ] && in_hoststatus=
        [ "${in_hoststatus-}" = 'all' ] && in_hoststatus=
        [ "${in_sshkey-}" = 'all' ] && in_sshkey=
	case "$in_action" in
		read)
			case "$in__objects" in
                                host/zabbix/mysql)
                                        read_zabbix_mysql_host "${in_hostid-0}" | _split_tabs | (
                                                IFS=$_IFS
                                                read -r id name ip dns status
                                                write_string_param 'id' "$id"
                                                write_string_param 'hostname' "$name"
                                                write_string_param 'ip' "$ip"
                                                write_string_param 'dnsname' "$dns"
                                                write_string_param 'status' "$status"
                                        )
                                        ;;
                                filter/zabbix/mysql)
                                        if [ -n "${in_filter-}" ]; then
                                                write_string_param 'sql' "$(echo "$in_filter" | translate_filter_zabbix_mysql)"
                                                write_string_param 'desc' "$(echo "$in_filter" | describe_filter_zabbix_mysql)"
                                        else
                                                write_string_param 'sql' ''
                                                write_string_param 'desc' ''
                                        fi
                                        ;;
                                hosts/stats/zabbix/mysql)
                                        [ -n "${in_hoststatus-}" ] && \
                                                in_filtersql="$(_zabbix_mysql_add_hoststatus_filter "$in_hoststatus" "${in_filtersql-}")"
                                        read_zabbix_mysql_stats '' "${in_host-}" "${in_group-}" "${in_filtersql-}" | (
                                                read -r okok mod dis unk other total
                                                write_string_param 'ok' "$okok"
                                                write_string_param 'modified' "$mod"
                                                write_string_param 'disabled' "$dis"
                                                write_string_param 'unknown' "$unk"
                                                write_string_param 'other' "$other"
                                                write_string_param 'total' "$total"
                                        )
					;;
                                sshkey/dir)
                                        write_string_param 'dir' "$(sshkey_dir "${in_sshkey-}")"
                                        ;;
                                dispatch)
                                        if [ -n "${DISPATCH_HEAD-}" ]; then
                                                read_${DISPATCH_SOURCE}_stats "$DISPATCH_HEAD" "$DISPATCH_HOST" "$DISPATCH_GROUP" "$DISPATCH_FILTERSQL" | (
                                                        read -r okok mod dis unk other total
                                                        write_string_param 'ok' "$okok"
                                                        write_string_param 'modified' "$mod"
                                                        write_string_param 'disabled' "$dis"
                                                        write_string_param 'unknown' "$unk"
                                                        write_string_param 'other' "$other"
                                                        write_string_param 'total' "$total"
                                                )
                                                write_string_param 'processed' "$(read_processed)"
                                                write_string_param 'failed' "$(read_failed)"
                                                write_string_param 'url' "$DISPATCH_URL"
                                                write_string_param 'head' "$DISPATCH_HEAD"
                                                write_string_param 'pid' "$(cat $DISPATCH_PIDFILE 2>/dev/null)"
                                        else
                                                write_string_param 'ok' 0
                                                write_string_param 'modified' 0
                                                write_string_param 'disabled' 0
                                                write_string_param 'unknown' 0
                                                write_string_param 'other' 0
                                                write_string_param 'total' 0
                                                write_string_param 'processed' 0
                                                write_string_param 'failed' 0
                                                write_string_param 'url' ""
                                                write_bool_param 'head' '#f'
                                                write_string_param 'pid' 0
                                        fi
                                        ;;
			esac
			;;
		write)
			case "$in__objects" in
				/)
					;;
                                sshkey/private)
                                        if [ -n "${in_sshkey-}" ]; then
                                                if is_ssh_private_key "$in_sshkey"; then
                                                        if [ "${in_delete-#f}" = "#t" ]; then
                                                                if [ "$(sshkey_dir "$in_sshkey")" = "mastercontrol" ]; then
                                                                        rm -f "$in_sshkey"
                                                                        rm -f "$in_sshkey.pub"
                                                                else
                                                                        write_error_message "`_ "Should not delete a user's personal key"`"
                                                                fi
                                                        else
                                                                install_ssh_key "$in_sshkey" "${in_passwd-}"
                                                        fi
                                                else
                                                        write_error_message "`_ 'The file is not an SSH private key'`"
                                                fi
                                        fi
                                        ;;
                                dispatch)
                                        if [ -n "${in_head-}" ] && [ "${in_head-}" != "#f" ] && [ -n "${in_hostlist-}" ]; then
                                                DISPATCH_HEAD="${in_head-}"
                                                DISPATCH_URL="${in_url-}"
                                                DISPATCH_HOST=
                                                DISPATCH_GROUP=
                                                DISPATCH_SOURCE=$(for srchostid in $in_hostlist; do echo ${srchostid%/*} | sed -e 's,/,_,g'; break; done)
                                                DISPATCH_FILTERSQL="$(hostlist_filter "$in_hostlist")"
                                                DISPATCH_SSHKEY="${in_sshkey}"
                                                if [ -n "$DISPATCH_FILTERSQL" ]; then
                                                        DISPATCH_PASSWD="${in_passwd}"
                                                        start_dispatch
                                                else
                                                        stop_dispatch
                                                        DISPATCH_HEAD=
                                                        DISPATCH_PASSWD=
                                                fi
                                        else
                                                stop_dispatch
                                                DISPATCH_HEAD=
                                                DISPATCH_PASSWD=
                                        fi
                                        ;;
                                dispatch/zabbix/mysql)
                                        if [ -n "${in_head-}" ] && [ "${in_head-}" != "#f" ]; then
                                                [ -n "${in_hoststatus-}" ] && \
                                                        in_filtersql="$(_zabbix_mysql_add_hoststatus_filter "$in_hoststatus" "${in_filtersql-}")"
                                                DISPATCH_URL="${in_url-}"
                                                DISPATCH_HEAD="${in_head-}"
                                                DISPATCH_HOST="${in_host-}"
                                                DISPATCH_GROUP="${in_group-}"
                                                DISPATCH_SOURCE=zabbix_mysql
                                                DISPATCH_FILTERSQL="${in_filtersql-}"
                                                DISPATCH_SSHKEY="${in_sshkey}"
                                                DISPATCH_PASSWD="${in_passwd}"
                                                start_dispatch
                                        else
                                                stop_dispatch
                                                DISPATCH_HEAD=
                                                DISPATCH_PASSWD=
                                        fi
                                        ;;
			esac
			;;
		list)
			case "$in__objects" in
                                params/zabbix/mysql)
                                        list_zabbix_mysql_items | (
                                                IFS=$_IFS
                                                _split_tabs | \
                                                while read -r id type minv avgv maxv label; do
                                                        write_table_item 'id' "$id" \
                                                                         'type' "$type" \
                                                                         'minval' "$(echo -e "$minv")" \
                                                                         'avgval' "$(echo -e "$avgv")" \
                                                                         'maxval' "$(echo -e "$maxv")" \
                                                                         'label' "$label"
                                                done
                                        )
                                        ;;
                                values/zabbix/mysql)
                                        list_zabbix_mysql_values "${in_hostid-0}" | (
                                                IFS=$_IFS
                                                _split_tabs | \
                                                while read -r id type val label; do
                                                        write_table_item 'id' "$id" \
                                                                         'type' "$type" \
                                                                         'val' "$(echo -e "$val")" \
                                                                         'label' "$label"
                                                done
                                        )
                                        ;;
                                groups/zabbix/mysql)
                                        write_enum_item 'ALL' "`_ 'All'`"
                                        list_zabbix_mysql_groups | write_enum
                                        ;;
                                hosts/zabbix/mysql)
                                        [ -n "${in_hoststatus-}" ] && \
                                                in_filtersql="$(_zabbix_mysql_add_hoststatus_filter "$in_hoststatus" "${in_filtersql-}")"
                                        list_zabbix_mysql_hosts "${in_host-}" "${in_group-}" "${in_filtersql-}" "${in_skip-0}" "${in_limit-25}" | (
                                                IFS=$_IFS
                                                _split_tabs | \
                                                while read -r id name addr profile current; do
                                                        write_table_item 'id' "$id" \
                                                                         'name' "$name" \
                                                                         'address' "$addr" \
                                                                         'profile' "$profile" \
                                                                         'current' "$(echo -e "$current")"
                                                done
                                        )
                                        ;;
                                sources)
                                        write_enum_item "zabbix/mysql" "zabbix/mysql"
                                        ;;
                                branches)
                                        (
                                                if ! git ls-remote --heads "${in_url-}" 2>/dev/null; then
                                                        write_error "`_ 'Unable to list branches for the given URL'`"
                                                        exit 1
                                                fi
                                        ) | sed -e 's,refs/heads/,,' | write_enum
                                        ;;
                                sshkeys/private)
                                        find "$SSH_KEYDIR" 2>/dev/null | \
                                        while read f; do
                                                if is_ssh_private_key "$f"; then
                                                        if [ -e "$f.pub" ]; then
                                                                write_enum_item "$f" "$(ssh_public_key_fingerprint "$f.pub")"
                                                        else
                                                                write_enum_item "$f" "${f##*/}"
                                                        fi
                                                fi
                                        done
                                        ;;
			esac
			;;
	esac
}

message_loop
