#!/bin/bash # ====================================================================== # # director helper # # C # R # U # D # actions for a host, commands, services # # This script contains specific logic for IML # - use puppet facts # - UniBe network and group names - see hostCreate() # # ---------------------------------------------------------------------- # ah = axel.hahn@iml.unibe.ch # 2022-02-16 v0.2 ah add --cfg param # 2022-03-04 v0.3 ah abort on http 5xx error # ====================================================================== tmpfile=/tmp/outcurl.tmp tmpfile2=/tmp/outcurl2.tmp # a custom file to source instead of detecting local data loadfile= # UNUSED # APICLIENT=`dirname $0`/api2director # MY_NAME=`facter fqdn | cut -f -2 -d ">"` # MY_IP=`facter ipaddress | cut -f -2 -d ">"` # TODO: test ... maybe uncomment it again # MY_NAME=`hostname -f` # MY_IP=`_getIpFrontend` typeset cfg_debug=false typeset cfg_dryrun=false # ====================================================================== # # FUNCTIONS # # ====================================================================== # helper to make http base setup for host actions function _initHttp(){ # see inc_functions _initHttpWithConfigfile "${dir_cfg}/api-director.cfg" if [ $cfg_debug = true ]; then http.setDebug 1 fi } function _initVars(){ if [ -z "$loadfile" ]; then MY_NAME=`hostname -f` MY_IP=`_getIpFrontend | head -1` MY_ZONE= fi IDC_host__cachefile="${dir_data}/${MY_NAME}__host_at-director.txt" IDC_service__cachefile="${dir_data}/${MY_NAME}__all_defined_services__at-director.txt" IDC_svcathost__cachefile="${dir_data}/${MY_NAME}__services_on_host__at-director.txt" # ch="`dirname $0`/inc/confighandler.sh" ch="./inc/confighandler.sh" } function flushDatadir(){ if [ ! -z "${dir_data}" -a -d "${dir_data}" ]; then _wd "deleting ${dir_data} ..." rm -f ${dir_data}/*.txt fi } # ---------------------------------------------------------------------- # functions for objects # ---------------------------------------------------------------------- # ............................................................ # set $ch to store all object vars function _generateJsonForHost(){ if [ -z "$loadfile" ]; then # --- host infos local MY_Platform=`uname -a | cut -f 1 -d ' '` . `dirname $0`/plugins/inc_pluginfunctions || exit 1 local MY_OSName=`ph.getOS` local MY_OSMajorVersion=`ph.getOSMajor` fi export CFGSTORAGE="directorhost" ( $ch --flush $ch --set object_name \"$MY_NAME\" $ch --set object_type '"object"' $ch --set address \"$MY_IP\" $ch --set display_name \"$MY_NAME\" $ch --set zone \"$MY_ZONE\" $ch --set icon_image \"/images/os/${MY_OSName}.png\" $ch --set icon_image_alt \"${MY_Platform}\:\ ${MY_OSName}\ ${MY_OSMajorVersion}\" ) 2>/dev/null # --- detect phase # local phase="live" # for myphase in preview stage demo # do # echo $MY_NAME | grep "\.$myphase\." >/dev/null && phase=$myphase # done ( # ----- facter data to host vars $ch --set vars.platform \"${MY_Platform}\" $ch --set vars.os \"${MY_OSName}${MY_OSMajorVersion}\" # ----- set host type # host in the UNIBE network: echo $MY_IP | grep -E "^(10\.|172\.1[6-9]\.|172.2[0-9]\.|172\.3[01]\.|192.168\.)" >/dev/null if [ $? -eq 0 ]; then $ch --set imports '["host passive only"]' else $ch --set imports '["host in network"]' # port checks initiated by icinga server to monitor client if [ ! -z "${host_vars_tcpport}" -a "${host_vars_tcpport}" != "[]" ]; then host_vars_tcpport=`echo ${host_vars_tcpport} | sed "s# ##g"` $ch --set vars.tcp_port ${host_vars_tcpport} fi fi # ----- /host type # ----- host groups # all host groups must exist in director - otherwise the creation # of a host will fail # see https://icinga.one.iml.unibe.ch/icingaweb2/director/dashboard?name=hosts#!/icingaweb2/director/hostgroups $ch --set groups '["iml", "iml-server"]' # ... and add some others # $ch --add groups \"iml-phase-$phase\" ) 2>/dev/null # ----- generate data and send to DIRECTOR # DATA=`$ch --json 2>/dev/null` # $ch --flush 2>/dev/null } # ............................................................ # set $ch to store all object vars # UNUSED see response of GET director/commands/templates # function _generateJsonForCommand(){ # export CFGSTORAGE="command-${IDC_command__obj_name}" # ( # $ch --flush # $ch --set object_name "\"${IDC_command__obj_name}\"" # $ch --set object_type \"template\" # # ) 2>/dev/null # } # ............................................................ # set $ch to store all object vars function _generateJsonForServicetemplate(){ export CFGSTORAGE="service-${IDC_service__obj_name}" ( $ch --flush $ch --set object_name "\"${IDC_service__obj_name}\"" $ch --set check_command \"${IDC_command__obj_name}\" $ch --set object_type \"template\" $ch --set enable_active_checks false $ch --set enable_passive_checks true $ch --set check_interval "\"${checkInterval}s\"" # see _parseCheckConfig FILE # graphite # $ch --set enable_perfdata true # $ch --set vars.check_command \"${checkName}\" # for graphite plugin if [ ! -z "${checkIcon}" ]; then $ch --set icon_image \"${checkIcon}\" $ch --set icon_image_alt \"${checkName}\" fi if [ ! -z "${checkMaxAttempts}" ]; then $ch --set max_check_attempts \"${checkMaxAttempts}\" fi ) 2>/dev/null } # ............................................................ # set $ch to store all object vars function _generateJsonForSvclink(){ export CFGSTORAGE="servicelink-${IDC_svcathost__obj_name}" ( $ch --flush $ch --set object_name "\"${IDC_svcathost__obj_name}\"" $ch --set object_type \"object\" $ch --set host "\"${MY_NAME}\"" $ch --set imports "[ \"${IDC_service__obj_name}\" ]" ) 2>/dev/null } # ............................................................ # CRUD actions for an director object: host, service template, linked service # examples: # ObjAction create host # ObjAction list svclink # # param string name of action; one of create|read|update|delete|exists|list # param string name of object; one of host|service|svclink # param string Dryrun (set any not empty value to show infos without execution) function ObjAction(){ local _paramAction=$1 local _paramObj=$2 local _paramDryrun=$cfg_dryrun test -z "$3" || _paramDryrun=true local _object_name= # name of the current object (for ouput only) local _sMethod= # http method; GET|POST|PUT|DELETE local _sUrl= # relative url (part behind REST API base url) local _jsondata= local _bNeedsCheck=false # requires $IDC* vars (a read check config first) local _bSendData=false # true for PUT and POST local _sUrlList= local _sUrlCreate= local _sUrlExists= local _sUrlRead= local _sUrlUpdate= local _sUrlDelete= local _existFilter= local _bShowResponse=true local _bShowFilter=false local _bStopOnError=true # local _sCachefile= _initHttp # --- init object based vars case "${_paramObj}" in 'host') _object_name=$MY_NAME _sUrlCreate=director/host _sUrlExists=director/host?name=${_object_name} _sUrlRead=director/host?name=${_object_name} _sUrlUpdate=director/host?name=${_object_name} _sUrlDelete=director/host?name=${_object_name} _existFilter="object_name" _generateJsonForHost ;; 'service') _object_name=${IDC_service__obj_name} _bNeedsCheck=true _sUrlList=director/services/templates _sUrlCreate=director/service _sUrlExists=director/service?name=${_object_name} _sUrlRead=director/service?name=${_object_name} _sUrlUpdate=director/service?name=${_object_name} _sUrlDelete=director/service?name=${_object_name} _existFilter='"object_name": ' _generateJsonForServicetemplate ;; 'svclink') _object_name=${IDC_svcathost__obj_name} _bNeedsCheck=true _sUrlList=director/services?host=$MY_NAME _sUrlCreate=director/service _sUrlExists="director/service?name=${IDC_svcathost__obj_name}&host=$MY_NAME" _sUrlRead="director/service?name=${IDC_svcathost__obj_name}&host=$MY_NAME" _sUrlUpdate="director/service?name=${IDC_svcathost__obj_name}&host=$MY_NAME" _sUrlDelete="director/service?name=${IDC_svcathost__obj_name}&host=$MY_NAME" # exists: # _existFilter="object_name.*${IDC_svcathost__obj_name}" _existFilter='"object_name": ' _generateJsonForSvclink ;; *) echo "ERROR: object [${_paramObj}] cannot be handled in ${FUNCNAME[0]} (yet?)." exit 1 esac # --- init method based vars case "${_paramAction}" in 'list') _sMethod=GET _sUrl=$_sUrlList _bShowResponse=false _bShowFilter=true _bNeedsCheck=false # ;; 'exists') _sMethod=GET _sUrl=$_sUrlExists _bShowResponse=false _bStopOnError=false ;; 'create') _sMethod=PUT _sUrl=$_sUrlCreate _bSendData=true ;; 'read') _sMethod=GET _sUrl=$_sUrlRead ;; 'update') _sMethod=POST _sUrl=$_sUrlUpdate _bSendData=true ;; 'delete') _sMethod=DELETE _sUrl=$_sUrlDelete ;; *) echo "ERROR: method [${_paramAction}] does not exist." exit 1 esac if [ -z "$_sUrl" ]; then echo "SKIP: Action [${_paramAction}] is not supported (yet?) for object type [${_paramObj}]" return fi if [ $_bNeedsCheck = true -a -z "${IDC_svcathost__obj_name}" ]; then echo "SKIP: you need to load a check before accessing [${_paramObj}]" return fi # --- get json data of object if [ $_bSendData = true ]; then _jsondata=`$ch --json 2>/dev/null` fi $ch --flush 2>/dev/null # --- http request if [ ${_paramDryrun} = false ]; then _wd ">>>>> $_paramAction $_paramObj [${_object_name}] >> $_sMethod $_sUrl $_jsondata" http.makeRequest "$_sMethod" "$_sUrl" "$_jsondata" if _bStopOnError && http.isServerError >/dev/null; then echo "CRITICAL ERROR: Director API request failed with a server error $_sMethod $_sUrl" exit 1 fi if [ $_bShowResponse = true ]; then http.getResponseHeader http.getResponse fi http.isOk >/dev/null else echo "DRYRUN: >>>>> $_paramAction $_paramObj [${_object_name}] >> $_sMethod $_sUrl $_jsondata" echo "... _bShowResponse: $_bShowResponse" echo "... _bSendData : $_bSendData" fi # --- on list action: filter response if [ "${_paramAction}" = "list" ]; then if [ ${_paramDryrun} = false ]; then _wd ">>>>> filter response by [object_name]" if [ $_bShowFilter = true ]; then http.getResponse | grep object_name | cut -f 2- -d ":" | sed 's#^ "##g' | sed 's#",$##' else http.getResponse | grep object_name | cut -f 2- -d ":" | sed 's#^ "##g' | sed 's#",$##' >/dev/null fi else echo "DRYRUN: >>>>> filter response by [object_name]" echo fi fi # --- on exist action: filter response if [ "${_paramAction}" = "exists" -a ! -z "${_existFilter}" ]; then if [ ${_paramDryrun} = false ]; then _wd ">>>>> filter response by [$_existFilter]" if [ $_bShowFilter = true ]; then http.getResponse | grep $_existFilter else http.getResponse | grep $_existFilter >/dev/null fi else echo "DRYRUN: >>>>> filter response by [$_existFilter]" echo fi fi } # ---------------------------------------------------------------------- # functions for Host # ---------------------------------------------------------------------- # ............................................................ # helper to create a base config for the current host # UNUSED function UNUSED_initHostdata(){ export CFGSTORAGE="directorhost" ( $ch --flush $ch --set object_name \"$MY_NAME\" $ch --set object_type '"object"' $ch --set address \"$MY_IP\" $ch --set display_name \"$MY_NAME\" ) 2>/dev/null } # ............................................................ # create a host with PUT on director API function hostCreate(){ _h2 "create host" ObjAction create host if [ -z "`http.isOk`" ]; then echo "ERROR, host was NOT created." else echo "OK, host was created successfully." fi } # ............................................................ # get data of current host from director API # and by the way it updates the local host infos too. function hostRead(){ _h2 "read host" ObjAction read host if [ -z "`http.isOk`" ]; then echo "ERROR, host was NOT found." fi } # ............................................................ # update current host # param JSON part starting with ", " and some json data function hostUpdate(){ _h2 "update host - set $1 $2" ObjAction update host if [ -z "`http.isOk`" ]; then case `http.getStatuscode` in "304") echo "OK, no update" ;; *) echo "ERROR during update of the host data" esac else echo "OK, host was updated" fi } # ............................................................ # ensure that a host exists function hostCreateOrUpdate(){ ObjAction exists host if [ $? -ne 0 ]; then hostCreate else hostUpdate fi } # ............................................................ # delete the current host in the director function hostDelete(){ _h2 "delete host" ObjAction delete host case `http.getStatuscode` in "200") echo "OK, host was deleted" ;; "404") echo "ERROR, host does not exist" ;; *) echo "ERROR, unable to delete host" esac flushDatadir } # ---------------------------------------------------------------------- # functions for services # director/service # ---------------------------------------------------------------------- # ............................................................ # helper for services: generate variable names for a check # vars have prefix IDC for "Icinga Director" # uses global var checkName function _generateVarsByCheckname(){ if [ -z "${checkName}" ]; then echo ERROR: checkName is empty - I guess _parseCheckConfig was not executed. exit 1 fi IDC_command__obj_name="${checkName}" IDC_service__obj_name="service-template_for_command_${checkName}" IDC_svcathost__obj_name="`_getName4Svcathost ${checkName}`" # IDC_service__obj_name="${checkName}" # IDC_svcathost__obj_name="${checkName}" IDC_command__cachefile="${dir_data}/command_${checkName}__at-director.txt" # IDC_service__cachefile="${dir_data}/all_defined_services__at-director.txt" # IDC_svcathost__cachefile="${dir_data}/all_services_on_host__at-director.txt" } # ............................................................ # create a service in Icinga director # uses global variables only function serviceCreateOrUpdate(){ _h2 "${FUNCNAME[0]}()" local sMode=update local sMethod=POST local _sUrl="director/service?name=${IDC_service__obj_name}" ObjAction exists service if [ $? -ne 0 ]; then ObjAction create service else ObjAction update service fi if [ $? -ne 0 ]; then echo "ERROR :/" else echo "OK" fi } # ............................................................ # create a service in Icinga director # param string filename of check config function serviceCreateByCfgFile(){ _h2 "${FUNCNAME[0]}($1) - create single service of given file" _parseCheckConfig "${1}" _generateVarsByCheckname "${checkName}" # create command if it does not exist serviceCreateOrUpdate ObjAction exists service if [ $? -eq 0 ]; then ObjAction exists svclink if [ $? -ne 0 ]; then ObjAction create svclink else echo "SKIP: linked service on host [${IDC_svcathost__obj_name}] exists" # TODO, uncomment -- wenn es sinnvolle Features gibt # ObjAction update svclink fi else echo "SKIP link ... service template for ${checkName} not ready." fi } # ............................................................ # create all functions; this functon is called with cli parameter # no params function servicesCreateAll(){ _h2 "${FUNCNAME[0]}() - create all services" # loop over all services and create for mycheckfile in `getChecks` do serviceCreateByCfgFile "${mycheckfile}" echo done } # ............................................................ # cleanup services - delete unneded links function svclinkCleanup(){ _h2 "Cleanup linked service templates" tmpRemote=/tmp/remoteLinks tmpLocal=/tmp/localChecks # --- perpare I: create file with remote service template links ObjAction list svclink >$tmpRemote # --- perpare II: create file with local configs and object names for its link rm -f $tmpLocal 2>/dev/null for mycheckfile in `getChecks` do _parseCheckConfig "${mycheckfile}" _generateVarsByCheckname "${checkName}" echo "$mycheckfile:${IDC_svcathost__obj_name}" >>$tmpLocal done # _h3 "local checks" # cat $tmpLocal # _h3 "remote linked service templates" # ObjAction list svclink # --- Compare ... # _h3 "Compare" cat $tmpRemote | while read remoteLink do grep $remoteLink $tmpLocal >/dev/null if [ $? -eq 0 ]; then echo "OK: $remoteLink" else echo "DELETE: link [$remoteLink] is not used by any local check anymore." checkName=`_getName4Svcathost $remoteLink reverse` _generateVarsByCheckname "${checkName}" ObjAction delete svclink fi done rm -f $tmpLocal $tmpRemote echo --- done. } # ---------------------------------------------------------------------- # functions for Director # ---------------------------------------------------------------------- # ............................................................ # kick the director to deploy config changes now function directorDeploy(){ _h2 deploy _initHttp _wd POST director/config/deploy _APIcall POST director/config/deploy if [ -z "`http.isOk`" ]; then echo "ERROR deploy config was not queued." else echo "OK, deploy was triggered" fi } # .................................................................. # # show a help function showHelp(){ script=`basename $0` cat <<ENDOFHELP HELP: Host actions --hc --hostcreate Create the host [$MY_NAME] in the icinga director --hr --hostread Read the host information of [$MY_NAME] in the icinga director --hu --hostupdate Update host in the Icinga director --he --hostensure Create host if it does not exist otherwise update it in the Icinga director --hd --hostdelete Delete [$MY_NAME] in the icinga director Check actions --listchecks list all local config files of known checks for [$MY_NAME] --linkcleanup verify added service templates on [$MY_NAME] with locally defined checks and remove unneeded items. --sca --servicescreateall Loop over defined checks and add all checks to the host. See also: --listchecks to get a list of config files. --sc CFGFILE --servicecreate CFGFILE create service by naming a config file See also: --listchecks to get a list of config files. Cache --flushcache remove files in [$dir_data] Director actions --deploy icinga update (deploy director data) Other parameters --cfg CONFIGFILE load a costom config file; default: ./inc_getconfig.sh This must be the 1st parameter to be processed. --debug enable debug output. --nodebug disable debug output. --dryrun enable dryrun - it shows actions without execution. EXAMPLES # $script --dryrun --he Show actions and api calls for "--he" parameter (create or update host) ENDOFHELP } # ------------------------------------------------------------ # # MAIN # # ------------------------------------------------------------ echo echo "##### DIRECTOR HELPER $MY_NAME - $MY_IP" echo if [ "$1" = "--cfg" ] && [ -n "$2" ]; then echo "INFO: loading custom config [$2]..." . "${2}" shift 2 else . "$( dirname $0 )/inc_getconfig.sh" fi . `dirname $0`/inc_functions.sh . `dirname $0`/inc/rest-api-client.sh if [ $# -eq 0 ]; then showHelp exit 0 fi cd `dirname $0` ls ./`basename $0` >/dev/null || exit 1 _initVars # ensure that ./inc_getconfig.sh was loaded if [ -z "${dir_cfg}" ]; then echo ERROR: Client is not installed/ configured yet on this machine. exit 1 fi while [ $# -gt 0 ]; do case "$1" in '--help' | '-h' | '-?') showHelp exit 0 ;; '--debug') cfg_debug=true ;; '--nodebug') cfg_debug=false ;; '--dryrun') cfg_dryrun=true ;; # ----- override local data with those from a file '--load') if [ ! -f "$2" ]; then echo ERROR: file "$2" is not readable. echo Hint: ist must be an absolute path or relative to $( pwd ) exit 1 fi loadfile="$2" . "${loadfile}" shift echo "loaded ${loadfile}" ;; # ----- host actions '--he' | '--hostensure') hostCreateOrUpdate ;; '--hc' | '--hostcreate') hostCreate ;; '--hr' | '--hostread') hostRead ;; '--hu' | '--hostupdate') hostUpdate ;; '--hd' | '--hostdelete') hostDelete ;; '--hs' | '--hostshow') _generateJsonForHost $ch --json 2>/dev/null ;; # ----- check actions '--listchecks') getChecks ;; '--sca' | '--servicescreateall') servicesCreateAll ;; '--sc' | '--servicecreate') serviceCreateByCfgFile "${2}" shift ;; '--linkcleanup') svclinkCleanup shift ;; '--flushcache') flushDatadir ;; # ----- director '--deploy') directorDeploy exit 0 ;; '--testrun') _h1 "svclink - without a config" ObjAction "create" "svclink" dry ObjAction "exists" "svclink" dry ObjAction "read" "svclink" dry ObjAction "update" "svclink" dry ObjAction "delete" "svclink" dry _h1 "svclink - with reading a config" _parseCheckConfig "/etc/icinga2-passive-client/checks/CPU-usage" _generateVarsByCheckname "${checkName}" ObjAction "create" "svclink" dry ObjAction "exists" "svclink" dry ObjAction "read" "svclink" dry ObjAction "update" "svclink" dry ObjAction "delete" "svclink" dry _h1 "host" ObjAction "create" "host" dry ObjAction "exists" "host" dry ObjAction "read" "host" dry ObjAction "update" "host" dry ObjAction "delete" "host" dry ;; # ---- ab hier TODO u) shift 1 hostUpdate "$*" exit 0 ;; *) echo "ERROR: unknown parameter detected. No idea what to do with [$1]." echo "Exiting..." exit 2 esac shift 1 done exit 0 # ======================================================================