#!/bin/bash

set -e

# ------------------------------------------------------------
# Company:  Software- und Organisations-Service GmbH
# Date:     2024-02-10
# Purpose:  Switch Subagent Cluster
# ------------------------------------------------------------
#
# Examples:
# ./switch-subagent-cluster.sh --url=https://joc-2-0-primary.sos:7443 --user=root --password=root --controller-id=testsuite --agent-cluster-id=agent_cluster_001 --subagent-cluster-id=active-passive-all-agents --check
#     displays the list of Subagents and priorities in the given Subagent Cluster

# ./switch-subagent-cluster.sh --url=https://joc-2-0-primary.sos:7443 --user=root --password=root --controller-id=testsuite --agent-cluster-id=agent_cluster_001 --subagent-cluster-id=active-passive-all-agents --switch
#     switches the active role from the Subagent with highest priority to the Subagent with next priority

# ./switch-subagent-cluster.sh --url=https://joc-2-0-primary.sos:7443 --user=root --password=root --controller-id=testsuite --agent-cluster-id=agent_cluster_001 --subagent-cluster-id=active-passive-all-agents --active=secondary_director_001
#     switches the active role to a specific Subagent in an active-passive Subagent Cluster

# ------------------------------
# Global script variables
# ------------------------------

joc_url=
joc_user=
joc_password=
joc_cacert=
joc_client_cert=
joc_client_key=
controller_id=
make_dirs=
show_logs=
log_dir=
verbose=

item=
start_time=$(date +"%Y-%m-%dT%H-%M-%S")
response_json=
access_token=
agent_cluster_id=
subagent_cluster_id=
active_subagent_id=
check=0
switch=0

# ------------------------------
# Inline Functions
# ------------------------------

AskPassword() {
    joc_password="$(
        exec < /dev/tty || exit
        tty_config=$(stty -g) || exit
        trap 'stty "$tty_config"' EXIT INT TERM
        stty -echo || exit
        printf 'Password: ' > /dev/tty
        IFS= read -r password; rc=$? 2> /dev/tty
        echo > /dev/tty
        printf '%s\n' "$password"
        exit "$rc"
    )"
}

Log()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "$@" >> "${log_file}"
    fi
    
    if [ -z "${show_logs}" ]
    then
        echo "$@"
    fi
}

LogVerbose()
{
    if [ -n "${verbose}" ]
    then
        if [ -n "${log_file}" ] && [ -f "${log_file}" ]
        then
            echo "$@" >> "${log_file}"
        fi
    
        if [ -z "${show_logs}" ]
        then
            echo "$@"
        fi
    fi
}

LogWarning()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "[WARN]" "$@" >> "${log_file}"
    fi
    
    >&2 echo "[WARN]" "$@"
}

LogError()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "[ERROR]" "$@" >> "${log_file}"
    fi
    
    >&2 echo "[ERROR]" "$@"
}

Login()
{ 
    LogVerbose ".. Login"
    curl_options=(-k -L -s -S -X POST -m 15)

    if [ -n "${joc_user}" ] && [ -n "${joc_password}" ]
    then
        curl_options+=(--user "${joc_user}":"${joc_password}")
    fi

    if [ "${joc_cacert}" != "" ]
    then
        curl_options+=(--cacert "${joc_cacert}")
    fi

    if [ "${joc_client_cert}" != "" ]
    then
        curl_options+=(--cert "${joc_client_cert}")
    fi

    if [ "${joc_client_key}" != "" ]
    then
        curl_options+=(--key "${joc_client_key}")
    fi

    LogVerbose ".... request:"
    LogVerbose "curl ${curl_options[*]} -H "Accept: application/json" -H "Content-Type: application/json" ${joc_url}/joc/api/authentication/login"

    response_json=$(curl ${curl_options[@]} -H "Accept: application/json" -H "Content-Type: application/json" "${joc_url}"/joc/api/authentication/login)
    LogVerbose ".... response:"
    LogVerbose "${response_json}"

    if echo "${response_json}" | jq -e . >/dev/null 2>&1
    then
        access_token=$(echo "${response_json}" | jq -r ".accessToken // empty" | sed 's/^"//' | sed 's/"$//')
        LogVerbose ".... access token: ${access_token}"
        if [ -z "${access_token}" ]
        then
            LogError "Login failed: ${response_json}"
            exit 4
        fi
    else
        LogError "Login failed: ${response_json}"
        exit 4
    fi
}

Logout()
{
    LogVerbose ".. Logout"
    curl_options=(-k -L -s -S -X POST -m 15)

    if [ "${joc_cacert}" != "" ]
    then
        curl_options+=(--cacert "${joc_cacert}")s
    fi

    if [ "${joc_client_cert}" != "" ]
    then
        curl_options+=(--cert "${joc_client_cert}")
    fi

    if [ "${joc_client_key}" != "" ]
    then
        curl_options+=(--key "${joc_client_key}")
    fi

    LogVerbose ".... request:"
    LogVerbose "curl ${curl_options[*]} -H \"X-Access-Token: ${access_token}\" -H \"Accept: application/json\" -H \"Content-Type: application/json\" ${joc_url}/joc/api/authentication/logout"

    response_json=$(curl ${curl_options[@]} -H "X-Access-Token: ${access_token}" -H "Accept: application/json" -H "Content-Type: application/json" "${joc_url}"/joc/api/authentication/logout)
    LogVerbose ".... response:"
    LogVerbose "${response_json}"

    if echo "${response_json}" | jq -e . >/dev/null 2>&1
    then
        item=$(echo "${response_json}" | jq -r "select(.isAuthenticated == false) // empty" | sed 's/^"//' | sed 's/"$//')
        if [ -z "${item}" ]
        then
            LogError "Logout failed: ${response_json}"
            exit 4
        fi
        access_token=
    else
        LogError "Logout failed: ${response_json}"
        exit 4
    fi
}

Get_Subagent_Cluster()
{
    LogVerbose ".. Get_Subagent_Cluster()"
    curl_options=(-k -L -s -S -X POST -m 15)

    if [ "${joc_cacert}" != "" ]
    then
        curl_options+=(--cacert "${joc_cacert}")
    fi

    if [ "${joc_client_cert}" != "" ]
    then
        curl_options+=(--cert "${joc_client_cert}")
    fi

    if [ "${joc_client_key}" != "" ]
    then
        curl_options+=(--key "${joc_client_key}")
    fi

    request_body="{ \"controllerId\": \"${controller_id}\", \"agentIds\": [\"${agent_cluster_id}\"], \"subagentClusterIds\": [\"${subagent_cluster_id}\"] }"

    LogVerbose ".... request:"
    LogVerbose "curl ${curl_options[*]} -H \"X-Access-Token: ${access_token}\" -H \"Accept: application/json\" -H \"Content-Type: application/json\" -d ${request_body} ${joc_url}/joc/api/agents/cluster"

    response_json=$(curl ${curl_options[@]} -H "X-Access-Token: ${access_token}" -H "Accept: application/json" -H "Content-Type: application/json" -d "${request_body}"  "${joc_url}"/joc/api/agents/cluster)

    LogVerbose ".... response:"
    LogVerbose "${response_json}"

    if echo "${response_json}" | jq -e . >/dev/null 2>&1
    then
        ok=$(echo "${response_json}" | jq -r ".subagentClusters // empty" | sed 's/^"//' | sed 's/"$//')
        if [ -z "${ok}" ]
        then
            error_code=$(echo "${response_json}" | jq -r ".error.code // empty" | sed 's/^"//' | sed 's/"$//')
            if [ "${error_code}" = "JOC-400" ]
            then
                LogWarning "Get_Subagent_Cluster() did not find results: ${response_json}"
                exit 3
            else
                LogError "Get_Subagent_Cluster() failed: ${response_json}"
                exit 4
            fi
        fi
    else
        LogError "Get_Subagent_Cluster() failed: ${response_json}"
        exit 4
    fi
}

Set_Subagent_Cluster()
{
    LogVerbose ".. Set_Subagent_Cluster()"
    curl_options=(-k -L -s -S -X POST -m 15)

    if [ "${joc_cacert}" != "" ]
    then
        curl_options+=(--cacert "${joc_cacert}")
    fi

    if [ "${joc_client_cert}" != "" ]
    then
        curl_options+=(--cert "${joc_client_cert}")
    fi

    if [ "${joc_client_key}" != "" ]
    then
        curl_options+=(--key "${joc_client_key}")
    fi

	high_priority=$(echo "${response_json}" | jq -r ".subagentClusters[].subagentIds | map(.priority | tonumber) | max")

    if ! [[ "${high_priority}" =~ ^[0-9]+$ ]]
    then
        LogError "Set_SubagentCluster() did not find numeric priority: ${response_json}"
        exit 4
    fi

    if [ "${switch}" = "1" ]
    then
        low_priority=$(( high_priority - 1 ))
        subagent_cluster=$(echo "${response_json}" | jq -r "(.subagentClusters | map(. as \$c | ( \$c.subagentIds | any(.priority|type==\"string\")) as \$isString | .subagentIds |= map(if (.priority|tonumber)==${high_priority} then if \$isString then .priority=(${low_priority}|tostring) else .priority=${low_priority} end elif (.priority|tonumber)==${low_priority} then if \$isString then .priority=(${high_priority}|tostring) else .priority=${high_priority} end else . end)))")
    else
		low_priority=$(echo "${response_json}" | jq -r --arg active_subagent_id "${active_subagent_id}" '.subagentClusters[].subagentIds[]|select(.subagentId==$active_subagent_id)|.priority|tonumber')
		subagent_cluster=$(echo "${response_json}" | jq -r "(.subagentClusters | map(. as \$c | ( \$c.subagentIds | any(.priority|type==\"string\")) as \$isString | .subagentIds |= map(if (.priority|tonumber)==${high_priority} then if \$isString then .priority=(${low_priority}|tostring) else .priority=${low_priority} end elif (.priority|tonumber)==${low_priority} then if \$isString then .priority=(${high_priority}|tostring) else .priority=${high_priority} end else . end)))")
    fi

    request_body="{ \"subagentClusters\": ${subagent_cluster}"
    request_body="${request_body}, \"auditLog\": { \"comment\": \"updated by switch-subagent-cluster.sh\" }"
    request_body="${request_body} }"

    echo "Switch : ${request_body}"

    LogVerbose ".... request:"
    LogVerbose "curl ${curl_options[*]} -H \"X-Access-Token: ${access_token}\" -H \"Accept: application/json\" -H \"Content-Type: application/json\" -d ${request_body} ${joc_url}/joc/api/agents/cluster/store"

    response_json=$(curl ${curl_options[@]} -H "X-Access-Token: ${access_token}" -H "Accept: application/json" -H "Content-Type: application/json" -d "${request_body}"  "${joc_url}"/joc/api/agents/cluster/store)

    LogVerbose ".... response:"
    LogVerbose "${response_json}"

    if echo "${response_json}" | jq -e . >/dev/null 2>&1
    then
        ok=$(echo "${response_json}" | jq -r ".ok // empty" | sed 's/^"//' | sed 's/"$//')
        if [ -z "${ok}" ]
        then
            error_code=$(echo "${response_json}" | jq -r ".error.code // empty" | sed 's/^"//' | sed 's/"$//')
            if [ "${error_code}" = "JOC-400" ]
            then
                LogWarning "Set_SubagentCluster() did not find orders: ${response_json}"
                exit 3
            else
                LogError "Set_SubagentCluster() failed: ${response_json}"
                exit 4
            fi
        fi
    else
        LogError "Set_SubagentCluster() failed: ${response_json}"
        exit 4
    fi

    request_body="{ \"controllerId\": \"${controller_id}\", \"subagentClusterIds\": [ \"${subagent_cluster_id}\"] }"

    LogVerbose ".... request:"
    LogVerbose "curl ${curl_options[*]} -H \"X-Access-Token: ${access_token}\" -H \"Accept: application/json\" -H \"Content-Type: application/json\" -d ${request_body} ${joc_url}/joc/api/agents/cluster/deploy"

    response_json=$(curl ${curl_options[@]} -H "X-Access-Token: ${access_token}" -H "Accept: application/json" -H "Content-Type: application/json" -d "${request_body}"  "${joc_url}"/joc/api/agents/cluster/deploy)

    LogVerbose ".... response:"
    LogVerbose "${response_json}"

    if echo "${response_json}" | jq -e . >/dev/null 2>&1
    then
        ok=$(echo "${response_json}" | jq -r ".ok // empty" | sed 's/^"//' | sed 's/"$//')
        if [ -z "${ok}" ]
        then
            error_code=$(echo "${response_json}" | jq -r ".error.code // empty" | sed 's/^"//' | sed 's/"$//')
            if [ "${error_code}" = "JOC-400" ]
            then
                LogWarning "Set_Subagent_Cluster() did not find subagent cluster: ${response_json}"
                exit 3
            else
                LogError "Set_Subagent_Cluster() failed: ${response_json}"
                exit 4
            fi
        fi
    else
        LogError "Set_Subagent_Cluster() failed: ${response_json}"
        exit 4
    fi

}

Usage()
{
    >&2 echo ""
    >&2 echo "Usage: $(basename "$0") [Options] [Switches]"
    >&2 echo ""
    >&2 echo "  Options:"
    >&2 echo "    --url=<url>                         | required: JOC Cockpit URL"
    >&2 echo "    --controller-id=<identifier>        | required: Controller ID"
    >&2 echo "    --user=<account>                    | required: JOC Cockpit user account"
    >&2 echo "    --password=<password>               | optional: JOC Cockpit password"
    >&2 echo "    --ca-cert=<path>                    | optional: path to CA Certificate used for JOC Cockpit login"
    >&2 echo "    --client-cert=<path>                | optional: path to Client Certificate used for login"
    >&2 echo "    --client-key=<path>                 | optional: path to Client Key used for login"
    >&2 echo "    --agent-cluster-id=<identifier>     | required: ID of the Agent Cluster"
    >&2 echo "    --subagent-cluster-id=<identifier>  | required: ID of the Subagent Cluster"
    >&2 echo "    --active=<identifier>               | optional: Identifier of the Subagent that should become active"
    >&2 echo "    --log-dir=<directory>               | optional: path to the directory holding the script's batch log files"
    >&2 echo ""
    >&2 echo "  Switches:"
    >&2 echo "    -h | --help                         | displays usage"
    >&2 echo "    -v | --verbose                      | displays verbose output"
    >&2 echo "    -p | --password                     | asks for password"
    >&2 echo "    -c | --check                        | checks which Subagents are configured and displays results"
    >&2 echo "    -s | --switch                       | specifies that active Subagent and next standby Subagent should be switched"
    >&2 echo "    --show-logs                         | shows log output if --log-dir is used"
    >&2 echo "    --make-dirs                         | creates directories if they do not exist"
    >&2 echo ""
}

Arguments()
{
    args="$*"
    for option in "$@"
    do
        case "${option}" in
            --url=*)                joc_url=$(echo "${option}" | sed 's/--url=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --user=*)               joc_user=$(echo "${option}" | sed 's/--user=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --password=*)           joc_password=$(echo "${option}" | sed 's/--password=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --cacert=*)             joc_cacert=$(echo "${option}" | sed 's/--cacert=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --client-cert=*)        joc_client_cert=$(echo "${option}" | sed 's/--client-cert=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --client-key=*)         joc_client_key=$(echo "${option}" | sed 's/--client-key=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --controller-id=*)      controller_id=$(echo "${option}" | sed 's/--controller-id=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --agent-cluster-id=*)   agent_cluster_id=$(echo "${option}" | sed 's/--agent-cluster-id=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --subagent-cluster-id=*)  subagent_cluster_id=$(echo "${option}" | sed 's/--subagent-cluster-id=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --active=*)             active_subagent_id=$(echo "${option}" | sed 's/--active=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --log-dir=*)            log_dir=$(echo "${option}" | sed 's/--log-dir=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            # Switches
            -h|--help)              Usage
                                    exit
                                    ;;
            -v|--verbose)           verbose=1
                                    ;;
            -p|--password)          AskPassword
                                    ;;
            -c|--check)             check=1
                                    ;;
            -s|--switch)            switch=1
                                    ;;
            --make-dirs)            make_dirs=1
                                    ;;
            --show-logs)            show_logs=1
                                    ;;
            *)                      >&2 echo "unknown option: ${option}"
                                    Usage
                                    exit 1
                                    ;;
        esac
    done

    if ! command -v jq &> /dev/null
    then
        LogError "jq utility not found"
        exit 1
    fi

    if [ -z "${joc_url}" ]
    then
        LogError "JOC Cockpit URL not speified: --url=<url>"
        Usage
        exit 1
    fi

    if [ -z "${controller_id}" ]
    then
        LogError "Controller ID must be specified: --controller-id=<identifier>"
        Usage
        exit 1
    fi

    if [ -z "${joc_user}" ]
    then
        LogError "JOC Cockpit user account not speified: --user=<account>"
        Usage
        exit 1
    fi

    if [ -n "${joc_cacert}" ] && [ ! -f "${joc_cacert}" ]
    then
        LogError "Root CA Certificate file not found: --cacert=${joc_cacert}"
        Usage
        exit 1
    fi

    if [ -n "${joc_client_cert}" ] && [ ! -f "${joc_client_cert}" ]
    then
        LogError "Client Certificate file not found: --client-cert=${joc_client_cert}"
        Usage
        exit 1
    fi

    if [ -n "${joc_client_key}" ] && [ ! -f "${joc_client_key}" ]
    then
        LogError "Client Private Key file not found: --client-key=${joc_client_key}"
        Usage
        exit 1
    fi

    if [ -n "${show_logs}" ] && [ -z "${log_dir}" ]
    then
        LogError "Log directory not specified and --show-logs switch is present: --log-dir="
        Usage
        exit 1
    fi

    if [ -z "${make_dirs}" ] && [ -n "${log_dir}" ] && [ ! -d "${log_dir}" ]
    then
        LogError "Log directory not found and --make-dirs switch not present: --log-dir=${log_dir}"
        Usage
        exit 1
    fi

    if [ -z "${agent_cluster_id}" ]
    then
        LogError "Argument has to be present: --agent-cluster-id=<identfier>"
        Usage
        exit 1
    fi

    if [ -z "${subagent_cluster_id}" ] && [ "${check}" = "0" ]
    then
        LogError "Argument has to be present if --check switch is not used: --subagent-cluster-id=<identfier>"
        Usage
        exit 1
    fi

    if [ -z "${active_subagent_id}" ] && [ "${switch}" = "0" ] && [ "${check}" = "0" ]
    then
        LogError "One of the arguments has to be present: --check, --switch or --active=<subagent-id>"
        Usage
        exit 1
    fi

    numOptions=$(( 0 + check + switch ))

    if [ "${numOptions}" -gt 1 ] 
    then
        LogError "Only one of the arguments can be present: --check or --switch"
        Usage
        exit 1
    fi
    
    if [ -n "${active_subagent_id}" ] && [ "${check}" = "1" ]
    then
        LogError "Only one of the arguments can be present: --check or --active=<agent-id>"
        Usage
        exit 1
    fi

    if [ -n "${active_subagent_id}" ] && [ "${switch}" = "1" ]
    then
        LogError "Only one of the arguments can be present: --switch or --active=<subagent-id>"
        Usage
        exit 1
    fi

    # initialize logging
    if [ -n "${log_dir}" ]
    then
        # create log directory if required
        if [ ! -d "${log_dir}" ] && [ -n "${make_dirs}" ]
        then
            mkdir -p "${log_dir}"
        fi
    
        log_file="${log_dir}"/create-flag-files."${start_time}".log
        while [ -f "${log_file}" ]
        do
            sleep 1
            start_time=$(date +"%Y-%m-%dT%H-%M-%S")
            log_file="${log_dir}"/create-flag-files."${start_time}".log
        done
        
        touch "${log_file}"
    fi

    Log "-- begin of log --------------"
    Log "$0" "$(echo "${args}" | sed 's/--password=\([^--]*\)//')"
    Log "-- begin of output -----------"
}

# ------------------------------
# Main
# ------------------------------

Process()
{
    LogVerbose ".. Processing"

    Login

    if [ "${check}" = "1" ]
    then
        Get_Subagent_Cluster
        echo "${response_json}" | jq -r ".subagentClusters // empty"
    else
        Get_Subagent_Cluster
        Set_Subagent_Cluster
    fi

    Logout
}

# ------------------------------
# Cleanup trap
# ------------------------------

End()
{
    if [ -n "${access_token}" ]
    then
        Logout
    fi

    if [ "$1" = "EXIT" ]
    then
        Log "-- end of log ----------------"

        if [ -n "${show_logs}" ] && [ -f "${log_file}" ]
        then
            cat "${log_file}"
        fi        
    fi

    unset joc_url
    unset joc_cacert
    unset joc_client_cert
    unset joc_client_key
    unset joc_user
    unset joc_password

    unset make_dirs
    unset show_logs
    unset verbose
    unset log_dir
    unset agent_cluster_id
    unset subagent_cluster_id
    unset active_subagent_id
    unset check
    unset switch

    unset log_file
    unset start_time

    unset response_json
    unset access_token
    unset curl_options

    set +e
}

# ------------------------------
# Enable trap and start
# ------------------------------

trap 'End EXIT' EXIT
trap 'End SIGTERM' TERM
trap 'End SIGINT' INT

Arguments "$@"
Process
