423 lines
11 KiB
Bash
423 lines
11 KiB
Bash
#!/bin/bash
|
|
# /usr/lib/check_mk_agent/local/300/check_portainer
|
|
|
|
set -u
|
|
|
|
CONFIG="/etc/check_mk/portainer.conf"
|
|
|
|
OK=0
|
|
WARN=1
|
|
CRIT=2
|
|
UNKNOWN=3
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ ! -r "$CONFIG" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - Configuration file ${CONFIG} missing or unreadable"
|
|
exit 0
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG"
|
|
|
|
PORTAINER_URL="${PORTAINER_URL:-https://127.0.0.1:9443}"
|
|
PORTAINER_TOKEN="${PORTAINER_TOKEN:-}"
|
|
PORTAINER_INSECURE="${PORTAINER_INSECURE:-yes}"
|
|
ALERT_STOPPED_CONTAINERS="${ALERT_STOPPED_CONTAINERS:-no}"
|
|
CHECK_UPDATES="${CHECK_UPDATES:-yes}"
|
|
PORTAINER_RELEASE_API="${PORTAINER_RELEASE_API:-https://api.github.com/repos/portainer/portainer/releases/latest}"
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Requirements
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - curl is not installed"
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - jq is not installed"
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v sort >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - sort is not installed"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "$PORTAINER_TOKEN" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - PORTAINER_TOKEN is not configured"
|
|
exit 0
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# curl configuration
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
CURL_OPTS=(
|
|
--silent
|
|
--show-error
|
|
--connect-timeout 3
|
|
--max-time 10
|
|
)
|
|
|
|
if [[ "$PORTAINER_INSECURE" == "yes" ]]; then
|
|
CURL_OPTS+=(--insecure)
|
|
fi
|
|
|
|
AUTH_HEADER="X-API-Key: ${PORTAINER_TOKEN}"
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Portainer health / version
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
STATUS_RESPONSE="$(
|
|
curl \
|
|
"${CURL_OPTS[@]}" \
|
|
"${PORTAINER_URL%/}/api/status" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$STATUS_RESPONSE" ]]; then
|
|
printf '%s\n' "${CRIT} \"Portainer\" - API unavailable at ${PORTAINER_URL}"
|
|
exit 0
|
|
fi
|
|
|
|
if ! printf '%s' "$STATUS_RESPONSE" | jq -e . >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer\" - Status API returned invalid JSON"
|
|
exit 0
|
|
fi
|
|
|
|
VERSION="$(
|
|
printf '%s' "$STATUS_RESPONSE" |
|
|
jq -r '.Version // .version // empty'
|
|
)"
|
|
|
|
if [[ -n "$VERSION" ]]; then
|
|
printf '%s\n' "${OK} \"Portainer\" - API reachable, version ${VERSION}"
|
|
else
|
|
printf '%s\n' "${OK} \"Portainer\" - API reachable"
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Portainer update check
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ "$CHECK_UPDATES" == "yes" ]]; then
|
|
|
|
if [[ -z "$VERSION" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Installed Portainer version could not be determined"
|
|
|
|
else
|
|
|
|
RELEASE_RESPONSE="$(
|
|
curl \
|
|
"${CURL_OPTS[@]}" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "User-Agent: checkmk-portainer-local-check" \
|
|
"$PORTAINER_RELEASE_API" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$RELEASE_RESPONSE" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Unable to retrieve latest release information"
|
|
|
|
elif ! printf '%s' "$RELEASE_RESPONSE" | jq -e . >/dev/null 2>&1; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Release API returned invalid JSON"
|
|
|
|
else
|
|
|
|
LATEST_VERSION="$(
|
|
printf '%s' "$RELEASE_RESPONSE" |
|
|
jq -r '.tag_name // empty'
|
|
)"
|
|
|
|
INSTALLED_VERSION="${VERSION#v}"
|
|
LATEST_VERSION="${LATEST_VERSION#v}"
|
|
|
|
if [[ -z "$LATEST_VERSION" ]]; then
|
|
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Latest release version could not be determined"
|
|
|
|
else
|
|
|
|
#
|
|
# Strip known suffixes before numeric comparison.
|
|
#
|
|
# Examples:
|
|
# 2.36.0
|
|
# 2.36.0-sts
|
|
# 2.36.0-lts
|
|
#
|
|
|
|
INSTALLED_COMPARE="${INSTALLED_VERSION%%-*}"
|
|
LATEST_COMPARE="${LATEST_VERSION%%-*}"
|
|
|
|
if [[ ! "$INSTALLED_COMPARE" =~ ^[0-9]+([.][0-9]+)*$ ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Unable to parse installed version ${INSTALLED_VERSION}"
|
|
|
|
elif [[ ! "$LATEST_COMPARE" =~ ^[0-9]+([.][0-9]+)*$ ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Portainer Update\" - Unable to parse latest version ${LATEST_VERSION}"
|
|
|
|
else
|
|
|
|
NEWEST="$(
|
|
printf '%s\n%s\n' \
|
|
"$INSTALLED_COMPARE" \
|
|
"$LATEST_COMPARE" |
|
|
sort -V |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ "$INSTALLED_COMPARE" == "$LATEST_COMPARE" ]]; then
|
|
|
|
printf '%s\n' \
|
|
"${OK} \"Portainer Update\" - Current: installed ${INSTALLED_VERSION}, latest ${LATEST_VERSION}"
|
|
|
|
elif [[ "$NEWEST" == "$LATEST_COMPARE" ]]; then
|
|
|
|
printf '%s\n' \
|
|
"${WARN} \"Portainer Update\" - Update available: installed ${INSTALLED_VERSION}, latest ${LATEST_VERSION}"
|
|
|
|
else
|
|
|
|
printf '%s\n' \
|
|
"${OK} \"Portainer Update\" - Installed ${INSTALLED_VERSION} is newer than latest stable ${LATEST_VERSION}"
|
|
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Endpoints
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
ENDPOINTS_RESPONSE="$(
|
|
curl \
|
|
"${CURL_OPTS[@]}" \
|
|
-H "$AUTH_HEADER" \
|
|
"${PORTAINER_URL%/}/api/endpoints" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$ENDPOINTS_RESPONSE" ]]; then
|
|
printf '%s\n' "${CRIT} \"Portainer Endpoints\" - Unable to retrieve endpoints"
|
|
exit 0
|
|
fi
|
|
|
|
if ! printf '%s' "$ENDPOINTS_RESPONSE" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"Portainer Endpoints\" - Endpoint API returned unexpected data"
|
|
exit 0
|
|
fi
|
|
|
|
ENDPOINT_COUNT="$(
|
|
printf '%s' "$ENDPOINTS_RESPONSE" |
|
|
jq 'length'
|
|
)"
|
|
|
|
UP_ENDPOINTS=0
|
|
DOWN_ENDPOINTS=0
|
|
|
|
while IFS= read -r ENDPOINT; do
|
|
|
|
ENDPOINT_ID="$(
|
|
printf '%s' "$ENDPOINT" |
|
|
jq -r '.Id // .ID // empty'
|
|
)"
|
|
|
|
ENDPOINT_NAME="$(
|
|
printf '%s' "$ENDPOINT" |
|
|
jq -r '.Name // "Unknown"'
|
|
)"
|
|
|
|
ENDPOINT_STATUS="$(
|
|
printf '%s' "$ENDPOINT" |
|
|
jq -r '.Status // 0'
|
|
)"
|
|
|
|
ENDPOINT_NAME="${ENDPOINT_NAME//\"/\'}"
|
|
|
|
#
|
|
# Portainer endpoint states:
|
|
# 1 = UP
|
|
# 2 = DOWN
|
|
#
|
|
|
|
if [[ "$ENDPOINT_STATUS" == "1" ]]; then
|
|
|
|
UP_ENDPOINTS=$((UP_ENDPOINTS + 1))
|
|
|
|
printf '%s\n' \
|
|
"${OK} \"Portainer Endpoint ${ENDPOINT_NAME}\" - Endpoint is UP"
|
|
|
|
else
|
|
|
|
DOWN_ENDPOINTS=$((DOWN_ENDPOINTS + 1))
|
|
|
|
printf '%s\n' \
|
|
"${CRIT} \"Portainer Endpoint ${ENDPOINT_NAME}\" - Endpoint is DOWN, status ${ENDPOINT_STATUS}"
|
|
|
|
fi
|
|
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
# Containers for this endpoint
|
|
# -----------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ -z "$ENDPOINT_ID" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Docker Containers ${ENDPOINT_NAME}\" - Endpoint ID missing"
|
|
continue
|
|
fi
|
|
|
|
CONTAINERS_RESPONSE="$(
|
|
curl \
|
|
"${CURL_OPTS[@]}" \
|
|
-H "$AUTH_HEADER" \
|
|
"${PORTAINER_URL%/}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=1" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$CONTAINERS_RESPONSE" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Docker Containers ${ENDPOINT_NAME}\" - Unable to retrieve containers"
|
|
continue
|
|
fi
|
|
|
|
if ! printf '%s' "$CONTAINERS_RESPONSE" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Docker Containers ${ENDPOINT_NAME}\" - Container API returned unexpected data"
|
|
continue
|
|
fi
|
|
|
|
TOTAL_CONTAINERS="$(
|
|
printf '%s' "$CONTAINERS_RESPONSE" |
|
|
jq 'length'
|
|
)"
|
|
|
|
RUNNING_CONTAINERS="$(
|
|
printf '%s' "$CONTAINERS_RESPONSE" |
|
|
jq '[.[] | select(.State == "running")] | length'
|
|
)"
|
|
|
|
STOPPED_CONTAINERS="$(
|
|
printf '%s' "$CONTAINERS_RESPONSE" |
|
|
jq '[.[] | select(.State != "running")] | length'
|
|
)"
|
|
|
|
UNHEALTHY_CONTAINERS="$(
|
|
printf '%s' "$CONTAINERS_RESPONSE" |
|
|
jq '
|
|
[
|
|
.[]
|
|
| select(
|
|
((.Status // "") | test("\\(unhealthy\\)"))
|
|
)
|
|
]
|
|
| length
|
|
'
|
|
)"
|
|
|
|
PROBLEM_LIST="$(
|
|
printf '%s' "$CONTAINERS_RESPONSE" |
|
|
jq -r '
|
|
.[]
|
|
| select(
|
|
(.State != "running")
|
|
or
|
|
((.Status // "") | test("\\(unhealthy\\)"))
|
|
)
|
|
| (
|
|
(
|
|
(.Names[0] // .Id // "unknown")
|
|
| sub("^/"; "")
|
|
)
|
|
+ "="
|
|
+ (.Status // .State // "unknown")
|
|
)
|
|
' |
|
|
paste -sd ';' -
|
|
)"
|
|
|
|
STATE=$OK
|
|
|
|
if [[ "$UNHEALTHY_CONTAINERS" -gt 0 ]]; then
|
|
STATE=$CRIT
|
|
|
|
elif [[ "$ALERT_STOPPED_CONTAINERS" == "yes" && "$STOPPED_CONTAINERS" -gt 0 ]]; then
|
|
STATE=$WARN
|
|
fi
|
|
|
|
PERFDATA="total=${TOTAL_CONTAINERS};;;0|running=${RUNNING_CONTAINERS};;;0|stopped=${STOPPED_CONTAINERS};;;0|unhealthy=${UNHEALTHY_CONTAINERS};;;0"
|
|
|
|
DETAILS="${RUNNING_CONTAINERS}/${TOTAL_CONTAINERS} running"
|
|
|
|
if [[ "$UNHEALTHY_CONTAINERS" -gt 0 ]]; then
|
|
DETAILS="${DETAILS}, ${UNHEALTHY_CONTAINERS} unhealthy"
|
|
fi
|
|
|
|
if [[ "$STOPPED_CONTAINERS" -gt 0 ]]; then
|
|
DETAILS="${DETAILS}, ${STOPPED_CONTAINERS} stopped"
|
|
fi
|
|
|
|
if [[ -n "$PROBLEM_LIST" ]]; then
|
|
DETAILS="${DETAILS}: ${PROBLEM_LIST}"
|
|
fi
|
|
|
|
printf '%s\n' \
|
|
"${STATE} \"Docker Containers ${ENDPOINT_NAME}\" ${PERFDATA} ${DETAILS}"
|
|
|
|
done < <(
|
|
printf '%s' "$ENDPOINTS_RESPONSE" |
|
|
jq -c '.[]'
|
|
)
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Endpoint summary
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
ENDPOINT_STATE=$OK
|
|
|
|
if [[ "$DOWN_ENDPOINTS" -gt 0 ]]; then
|
|
ENDPOINT_STATE=$CRIT
|
|
fi
|
|
|
|
printf '%s\n' \
|
|
"${ENDPOINT_STATE} \"Portainer Endpoints\" total=${ENDPOINT_COUNT};;;0|up=${UP_ENDPOINTS};;;0|down=${DOWN_ENDPOINTS};;;0 ${UP_ENDPOINTS}/${ENDPOINT_COUNT} endpoints UP"
|
|
|
|
exit 0 |