Select Git revision
director-cli.sh
-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
director-cli.sh 23.29 KiB
#!/bin/bash
# ======================================================================
#
# director helper
#
# C
# R
# U
# D
# actions for a host, commands, services
#
# This script contains specific logic for IML
# - UniBe network and group names - see hostCreate()
#
# ----------------------------------------------------------------------
# ah = axel.hahn@unibe.ch
# 2022-02-16 v0.2 ah add --cfg param
# 2022-03-04 v0.3 ah abort on http 5xx error
# 2023-01-22 v0.4 ah fix _bStopOnError; some shellfixes
# 2023-02-17 v0.5 ah add hostnamme into CFGSTORAGE
# 2023-02-17 v0.6 ah remove invalid line in cleanup
# 2023-02-17 v0.7 ah check inc_getconfig.sh exists and hostname -f has a value
# 2023-10-25 v0.8 ah generate json with "jo"; optimze http requests on services; parallel service function call
# ======================================================================
_version="0.8"
which curl >/dev/null || exit 1
which jo >/dev/null || exit 1
# a custom file to source instead of detecting local data
loadfile=
# 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
export regexPrivateNetworks="^(10\.|172\.1[6-9]\.|172.2[0-9]\.|172\.3[01]\.|192.168\.)"
# json for a host: set group if the system is pingable by Icinga or in a private network
hostImportReachable="host in network"
hostImportPrivate="host passive only"
# default groups (jo params)
hostDefaultgroups="groups[]=iml groups[]=iml-server"
# ======================================================================
#
# 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
# ch="`dirname $0`/inc/confighandler.sh"
ch="./inc/confighandler.sh"
}
function flushDatadir(){
if [ -n "${dir_data}" -a -d "${dir_data}" ]; then
_wd "deleting ${dir_data} ..."
rm -f ${dir_data}/*.txt
fi
}
# ----------------------------------------------------------------------
# functions for objects
# ----------------------------------------------------------------------
# ............................................................
# generate json parameters for jo
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
local JSONPARAMS="
object_name=$MY_NAME
object_type=object
address=$MY_IP
display_name=$MY_NAME
zone=$MY_ZONE
icon_image=/images/os/${MY_OSName}.png
icon_image_alt="\"${MY_Platform}:\ ${MY_OSName}\ ${MY_OSMajorVersion}\""
vars.platform=${MY_Platform} \
vars.os=${MY_OSName}${MY_OSMajorVersion}
"
# ----- set host type
# check if host is in a private network:
if echo "$MY_IP" | grep -E "${regexPrivateNetworks}" >/dev/null
then
JSONPARAMS+='imports[]="${hostImportPrivate}" '
else
JSONPARAMS+='imports[]="${hostImportReachable}" '
fi
# port checks initiated by icinga server to monitor client
if [ -n "${host_vars_tcpport}" -a "${host_vars_tcpport}" != "[]" ]; then
JSONPARAMS+='vars.tcp_port=${host_vars_tcpport} '
fi
# ----- host groups
# all host groups must exist in director - otherwise the creation
# of a host will fail
JSONPARAMS+="${hostDefaultgroups} "
echo "$JSONPARAMS"
}
# ............................................................
# generate json parameters for jo
# global int checkInterval fetched in check configuration by _parseCheckConfig
# global string IDC_service__obj_name generated var from check name in _generateVarsByCheckname
# global string IDC_command__obj_name generated var from check name in _generateVarsByCheckname
function _generateJsonForServicetemplate(){
local JSONPARAMS="
object_name="\"${IDC_service__obj_name}\""
check_command=\""${IDC_command__obj_name}\""
object_type=template
enable_active_checks=false
enable_passive_checks=true
check_interval="\"${checkInterval}s\""
# graphite
enable_perfdata=true
vars.check_command=\"${checkName}\"
"
if [ ! -z "${checkIcon}" ]; then
JSONPARAMS+="icon_image=\"${checkIcon}\" icon_image_alt=\"${checkName}\" "
fi
if [ ! -z "${checkMaxAttempts}" ]; then
JSONPARAMS+="max_check_attempts=${checkMaxAttempts}"
fi
echo "$JSONPARAMS"
}
# ............................................................
# generate json parameters for jo
function _generateJsonForSvclink(){
local JSONPARAMS="
object_name="\"${IDC_svcathost__obj_name}\""
check_type=object
host=${MY_NAME}
imports[]="\"${IDC_service__obj_name}\""
"
echo "JSONPARAMS"
}
# ............................................................
# 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"
_jsonparams="$( _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": '
_jsonparams="$( _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": '
_jsonparams="$( _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=$( eval jo -p -d. $_jsonparams )
fi
# --- 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
# ----------------------------------------------------------------------
# ............................................................
# 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_command__cachefile="${dir_data}/command_${checkName}__at-director.txt"
# set | grep "^IDC"
}
# ............................................................
# create a service in Icinga director
# uses global variables only
function serviceCreateOrUpdate(){
# _h2 "${FUNCNAME[0]}()"
echo ">>> ${FUNCNAME[0]}() - ${IDC_service__obj_name}"
local _action=create
if ObjAction exists service; then
_action=update
fi
if ObjAction $_action service; then
echo "OK $_action service ${IDC_service__obj_name}"
return 0
else
echo "ERROR :/ $_action service ${IDC_service__obj_name}"
return 1
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"
echo ">>>>>> ${FUNCNAME[0]}($1) - create single service of given file"
_parseCheckConfig "${1}"
_generateVarsByCheckname "${checkName}"
# create command if it does not exist
if serviceCreateOrUpdate >/dev/null ; then
if ! ObjAction exists svclink; then
if ObjAction create svclink; then
echo "OK: linked service ${checkName} on host [$MY_NAME] was created"
else
echo "ERROR :-/ unable to create linked service ${checkName} on host [$MY_NAME]"
fi
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: service ${checkName} not ready - unable to create a service link."
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}" &
done
# wait until all background processes are done.
# 1 line in output is the header and one sub process is the ps command --> check > 2 items
local _iProcesses=$( getChecks | wc -w )
while [ $_iProcesses -gt 2 ]; do
echo "Processes: $_iProcesses"
sleep 2
_iProcesses=$( ps --ppid "$$" | wc -l )
done
echo "Done ${FUNCNAME[0]}"
}
# ............................................................
# cleanup services - delete unneded links
function svclinkCleanup(){
_h2 "Cleanup linked service templates"
tmpRemote=/tmp/remoteLinks_${MY_NAME}.tmp
tmpLocal=/tmp/localChecks_${MY_NAME}.tmp
# --- perpare I: create file with remote service template links
ObjAction list svclink | grep -v '>>>' >$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
if grep "$remoteLink" $tmpLocal >/dev/null
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 v$_version ##### $MY_NAME $MY_IP"
echo
if [ "$1" = "--cfg" ] && [ -n "$2" ]; then
echo "INFO: loading custom config [$2]..."
. "${2}"
shift 2
else
_cfg="$( dirname $0 )/inc_getconfig.sh"
if [ ! -f "$_cfg" ]; then
echo "ERROR: The configuration file $_cfg was not created yet."
exit 1
fi
. "$_cfg"
fi
. $(dirname $0)/inc_functions.sh
. $(dirname $0)/inc/rest-api-client.sh
if [ $# -eq 0 ]; then
showHelp
exit 0
fi
cd $(dirname $0) || exit
ls ./$(basename $0) >/dev/null || exit 1
_initVars
if [ -z "$MY_NAME" ]; then
echo "ERROR: hostname is empty. Ensure that the command 'hostname -f' returns a fqdn."
exit 1
fi
# 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')
eval jo -p -d. $( _generateJsonForHost )
;;
# ----- 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
# ======================================================================