first commit
This commit is contained in:
19
.env
Normal file
19
.env
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# ===== RabbitMQ (HTTP Management API) =====
|
||||||
|
# If RabbitMQ is on the same VM, leave localhost and keep network_mode: host in compose.
|
||||||
|
RMQ_HOST=localhost
|
||||||
|
RMQ_PORT=15672
|
||||||
|
|
||||||
|
# Management user/pass
|
||||||
|
RMQ_USER=admin
|
||||||
|
RMQ_PASS=change_me
|
||||||
|
|
||||||
|
# Virtual host and queue to poll
|
||||||
|
VHOST=app
|
||||||
|
QUEUE=queue_controls
|
||||||
|
|
||||||
|
# Poll interval (seconds) when queue is empty
|
||||||
|
SLEEP_SECS=2
|
||||||
|
|
||||||
|
# ===== NetBox =====
|
||||||
|
NB_URL=http://netbox.gt-tiso.ikeja.co.za
|
||||||
|
NB_TOKEN=7648e4f5ee370cda7834682e61b47c2ee8e95623
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
data/
|
||||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV APP_DIR=/opt/containers/netbox-reporter
|
||||||
|
ENV DATA_DIR=${APP_DIR}/data
|
||||||
|
|
||||||
|
# Minimal deps
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
bash curl jq ca-certificates tini \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# App payload (immutable inside image)
|
||||||
|
RUN mkdir -p ${APP_DIR}/app/bin
|
||||||
|
COPY files/netbox-reporter.sh ${APP_DIR}/app/bin/netbox-reporter.sh
|
||||||
|
COPY files/entrypoint.sh /usr/local/bin/netbox-reporter-entrypoint
|
||||||
|
RUN chmod +x /usr/local/bin/netbox-reporter-entrypoint \
|
||||||
|
&& chmod +x ${APP_DIR}/app/bin/netbox-reporter.sh
|
||||||
|
|
||||||
|
WORKDIR ${APP_DIR}/app
|
||||||
|
|
||||||
|
# Use tini as PID1; entrypoint validates env and execs the main script
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/netbox-reporter-entrypoint"]
|
||||||
|
CMD ["/opt/containers/netbox-reporter/app/bin/netbox-reporter.sh"]
|
||||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
services:
|
||||||
|
netbox-reporter:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: netbox-reporter:latest
|
||||||
|
|
||||||
|
# Share host network so "localhost:15672" talks to RabbitMQ on the host
|
||||||
|
network_mode: host
|
||||||
|
|
||||||
|
working_dir: /opt/containers/netbox-reporter/app
|
||||||
|
entrypoint: ["/usr/bin/tini", "--", "/usr/local/bin/netbox-reporter-entrypoint"]
|
||||||
|
command: ["/opt/containers/netbox-reporter/app/bin/netbox-reporter.sh"]
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
|
|
||||||
|
# Load ALL runtime parameters from .env (one file to rule them all)
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./data:/opt/containers/netbox-reporter/data:rw
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS -u \"$RMQ_USER:$RMQ_PASS\" \"http://$RMQ_HOST:$RMQ_PORT/api/overview\" >/dev/null"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
28
files/entrypoint.sh
Executable file
28
files/entrypoint.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log(){ echo "[netbox-reporter][entrypoint] $*"; }
|
||||||
|
err(){ echo "[netbox-reporter][entrypoint][ERROR] $*" >&2; }
|
||||||
|
|
||||||
|
# Sanity: tools
|
||||||
|
for bin in curl jq; do
|
||||||
|
command -v "$bin" >/dev/null 2>&1 || { err "Missing $bin"; exit 2; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# Required env (all fed from .env via compose)
|
||||||
|
: "${RMQ_USER:?RMQ_USER required}"
|
||||||
|
: "${RMQ_PASS:?RMQ_PASS required}"
|
||||||
|
: "${RMQ_HOST:?RMQ_HOST required}"
|
||||||
|
: "${RMQ_PORT:?RMQ_PORT required}"
|
||||||
|
: "${VHOST:?VHOST required}"
|
||||||
|
: "${QUEUE:?QUEUE required}"
|
||||||
|
: "${SLEEP_SECS:?SLEEP_SECS required}"
|
||||||
|
|
||||||
|
: "${NB_URL:?NB_URL required}"
|
||||||
|
: "${NB_TOKEN:?NB_TOKEN required}"
|
||||||
|
|
||||||
|
log "RMQ=http://${RMQ_HOST}:${RMQ_PORT} vhost='${VHOST}' queue='${QUEUE}'"
|
||||||
|
log "NB_URL=${NB_URL}"
|
||||||
|
|
||||||
|
# Hand off to main script
|
||||||
|
exec "$@"
|
||||||
504
files/netbox-reporter.sh
Normal file
504
files/netbox-reporter.sh
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Minimal deps check ---
|
||||||
|
for bin in curl jq; do
|
||||||
|
command -v "$bin" >/dev/null 2>&1 || { echo "[ERROR] Missing dependency: $bin" >&2; exit 2; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- RabbitMQ config (override via env) ---
|
||||||
|
RMQ_USER="${RMQ_USER:-admin}"
|
||||||
|
RMQ_PASS="${RMQ_PASS:-change_me}"
|
||||||
|
RMQ_HOST="${RMQ_HOST:-localhost}"
|
||||||
|
RMQ_PORT="${RMQ_PORT:-15672}"
|
||||||
|
VHOST="${VHOST:-app}"
|
||||||
|
QUEUE="${QUEUE:-queue_controls}"
|
||||||
|
SLEEP_SECS="${SLEEP_SECS:-2}"
|
||||||
|
|
||||||
|
# --- NetBox config (override via env) ---
|
||||||
|
NB_URL="${NB_URL:-http://netbox.gt-tiso.ikeja.co.za}"
|
||||||
|
# Tip: Prefer setting NB_TOKEN via environment. Leaving default blank avoids accidental leaks.
|
||||||
|
NB_TOKEN="${NB_TOKEN:-}"
|
||||||
|
|
||||||
|
log() { echo "[netbox-reporter] $*"; }
|
||||||
|
warn(){ echo "[netbox-reporter][WARN] $*" >&2; }
|
||||||
|
err() { echo "[netbox-reporter][ERROR] $*" >&2; }
|
||||||
|
|
||||||
|
# Require NB_TOKEN
|
||||||
|
if [[ -z "${NB_TOKEN}" ]]; then
|
||||||
|
err "NB_TOKEN is not set. Export NB_TOKEN and retry."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# URL-encode for RabbitMQ HTTP API paths
|
||||||
|
urlenc() { printf '%s' "$1" | sed -e 's, ,%20,g' -e 's,/,%2F,g'; }
|
||||||
|
|
||||||
|
# Simple RabbitMQ HTTP call
|
||||||
|
rmq_api() {
|
||||||
|
local method="$1" path="$2" data="${3:-}"
|
||||||
|
local url="http://${RMQ_HOST}:${RMQ_PORT}${path}"
|
||||||
|
if [[ -n "$data" ]]; then
|
||||||
|
curl -sS -u "$RMQ_USER:$RMQ_PASS" -H "content-type:application/json" -X "$method" "$url" -d "$data"
|
||||||
|
else
|
||||||
|
curl -sS -u "$RMQ_USER:$RMQ_PASS" -H "content-type:application/json" -X "$method" "$url"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find NetBox device ID by exact name
|
||||||
|
nb_find_device_id() {
|
||||||
|
local name="$1"
|
||||||
|
local resp
|
||||||
|
resp="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/?name=$(printf '%s' "$name" | jq -sRr @uri)&limit=1")" || return 1
|
||||||
|
jq -r '.results[0].id // empty' <<<"$resp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH NetBox custom fields (update_wo_restart)
|
||||||
|
nb_patch_update_wo_restart() {
|
||||||
|
local dev_id="$1" update_progress="$2" updating_to="$3"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg p "$update_progress" --arg u "$updating_to" \
|
||||||
|
'{custom_fields: {update_progress: $p, updating_to: $u}}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Updated device id=$dev_id custom_fields OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to update device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH NetBox custom field: wifidebug (deploy_wifidebug)
|
||||||
|
nb_patch_wifidebug() {
|
||||||
|
local dev_id="$1" value="$2"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg v "$value" '{custom_fields: {wifidebug: $v}}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Updated device id=$dev_id wifidebug='$value' OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set wifidebug for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST a Journal Entry on the device (journal_add) with prefix "updated: "
|
||||||
|
nb_add_journal() {
|
||||||
|
local dev_id="$1" text="$2"
|
||||||
|
local comment="updated: ${text}"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg c "$comment" --argjson oid "$dev_id" \
|
||||||
|
'{assigned_object_type:"dcim.device", assigned_object_id:$oid, comments:$c}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/journal-entries/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ || "$code" == "201" ]]; then
|
||||||
|
log "Journal added for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to add journal for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST a Journal Entry with raw text (no prefix) — used for task_notify path
|
||||||
|
nb_add_journal_raw() {
|
||||||
|
local dev_id="$1" text="$2"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg c "$text" --argjson oid "$dev_id" \
|
||||||
|
'{assigned_object_type:"dcim.device", assigned_object_id:$oid, comments:$c}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/journal-entries/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ || "$code" == "201" ]]; then
|
||||||
|
log "Journal (raw) added for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to add raw journal for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH custom field: multiple_ssids = "yes" — used for task_notify path
|
||||||
|
nb_set_multiple_ssids_yes() {
|
||||||
|
local dev_id="$1"
|
||||||
|
local body='{"custom_fields":{"multiple_ssids":"yes"}}'
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Set multiple_ssids='yes' for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set multiple_ssids for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Add a tag to a device using tag slug (ensures tag exists; merges dictionaries) ---
|
||||||
|
nb_add_tag_by_slug() {
|
||||||
|
local dev_id="$1" tag_slug="$2"
|
||||||
|
|
||||||
|
# 1) Lookup tag by slug
|
||||||
|
local tag_resp tag_id tag_name
|
||||||
|
tag_resp="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/extras/tags/?slug=$(printf '%s' "$tag_slug" | jq -sRr @uri)&limit=1")" || tag_resp='{}'
|
||||||
|
tag_id="$(jq -r '.results[0].id // empty' <<<"$tag_resp")"
|
||||||
|
tag_name="$(jq -r '.results[0].name // empty' <<<"$tag_resp")"
|
||||||
|
|
||||||
|
# 2) Create tag if missing
|
||||||
|
if [[ -z "$tag_id" ]]; then
|
||||||
|
local derived_name create_body create_tmp create_code
|
||||||
|
derived_name="$(printf '%s' "$tag_slug" | sed -E 's/-+/ /g')"
|
||||||
|
create_body="$(jq -n --arg name "$derived_name" --arg slug "$tag_slug" '{name:$name, slug:$slug}')"
|
||||||
|
create_tmp="$(mktemp)"
|
||||||
|
create_code="$(curl -sS -o "$create_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/tags/" \
|
||||||
|
-d "$create_body" || true)"
|
||||||
|
if [[ "$create_code" != "201" && ! "$create_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
err "Failed to create tag slug='$tag_slug' (HTTP $create_code)"
|
||||||
|
cat "$create_tmp" >&2 || true
|
||||||
|
rm -f "$create_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
tag_name="$(jq -r '.name' < "$create_tmp")"
|
||||||
|
rm -f "$create_tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) Read existing device tags, normalize to [{name, slug}]
|
||||||
|
local dev_detail existing_objs_json
|
||||||
|
dev_detail="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/$dev_id/")"
|
||||||
|
existing_objs_json="$(
|
||||||
|
jq -c '
|
||||||
|
(.tags // []) as $t |
|
||||||
|
if ( ($t|length)>0 and (($t[0]|type)=="object") ) then
|
||||||
|
[ $t[] | {name:.name, slug:.slug} ]
|
||||||
|
else
|
||||||
|
[ $t[] | {name:.} ]
|
||||||
|
end
|
||||||
|
' <<<"$dev_detail"
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 4) Merge + dedupe; build PATCH body
|
||||||
|
local merged_objs_json patch_body patch_tmp patch_code
|
||||||
|
merged_objs_json="$(
|
||||||
|
jq -cn --arg name "$tag_name" --arg slug "$tag_slug" --argjson existing "$existing_objs_json" '
|
||||||
|
($existing + [ {name:$name, slug:$slug} ])
|
||||||
|
| group_by(.name)
|
||||||
|
| map(.[0])
|
||||||
|
'
|
||||||
|
)"
|
||||||
|
patch_body="$(jq -n --argjson tags "$merged_objs_json" '{tags:$tags}')"
|
||||||
|
|
||||||
|
# 5) PATCH device
|
||||||
|
patch_tmp="$(mktemp)"
|
||||||
|
patch_code="$(curl -sS -o "$patch_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$patch_body" || true)"
|
||||||
|
|
||||||
|
if [[ "$patch_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Tag '$tag_slug' applied to device id=$dev_id"
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set tag '$tag_slug' for device id=$dev_id (HTTP $patch_code)"
|
||||||
|
cat "$patch_tmp" >&2 || true
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- remove a tag from a device by slug (idempotent) ---
|
||||||
|
# --- remove a tag from a device by slug (idempotent) ---
|
||||||
|
nb_remove_tag_by_slug() {
|
||||||
|
local dev_id="$1" tag_slug="$2"
|
||||||
|
|
||||||
|
# 1) Get current tags
|
||||||
|
local dev_detail existing_objs_json
|
||||||
|
dev_detail="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/$dev_id/")" || return 1
|
||||||
|
|
||||||
|
# Normalize tags to objects: [{name, slug?}]
|
||||||
|
existing_objs_json="$(
|
||||||
|
jq -c '
|
||||||
|
(.tags // []) as $t |
|
||||||
|
if ( ($t|length)>0 and (($t[0]|type)=="object") ) then
|
||||||
|
[ $t[] | {name:(.name//""), slug:(.slug//"")} ]
|
||||||
|
else
|
||||||
|
[ $t[] | {name:.} ]
|
||||||
|
end
|
||||||
|
' <<<"$dev_detail"
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 2) Build a friendly name from slug (e.g. indoor-restart-scheduled -> indoor restart scheduled)
|
||||||
|
local name_from_slug
|
||||||
|
name_from_slug="$(sed -E 's/-+/ /g' <<<"$tag_slug")"
|
||||||
|
|
||||||
|
# 3) Filter out any tag whose slug == tag_slug OR name == tag_slug OR name == name_from_slug (case-insensitive)
|
||||||
|
local filtered_objs_json
|
||||||
|
filtered_objs_json="$(
|
||||||
|
jq -cn --arg slug "$tag_slug" --arg name "$name_from_slug" --argjson existing "$existing_objs_json" '
|
||||||
|
[ $existing[]
|
||||||
|
| select(
|
||||||
|
((.slug // "" | ascii_downcase) != ($slug | ascii_downcase))
|
||||||
|
and ((.name // "" | ascii_downcase) != ($slug | ascii_downcase))
|
||||||
|
and ((.name // "" | ascii_downcase) != ($name | ascii_downcase))
|
||||||
|
)
|
||||||
|
]
|
||||||
|
'
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 4) PATCH back
|
||||||
|
local patch_body patch_tmp patch_code
|
||||||
|
patch_body="$(jq -n --argjson tags "$filtered_objs_json" '{tags:$tags}')"
|
||||||
|
|
||||||
|
patch_tmp="$(mktemp)"
|
||||||
|
patch_code="$(curl -sS -o "$patch_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$patch_body" || true)"
|
||||||
|
|
||||||
|
if [[ "$patch_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Removed tag '$tag_slug' from device id=$dev_id (if present)"
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to remove tag '$tag_slug' for device id=$dev_id (HTTP $patch_code)"
|
||||||
|
cat "$patch_tmp" >&2 || true
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- clear only custom_field update_progress (set to null) ---
|
||||||
|
nb_clear_update_progress() {
|
||||||
|
local dev_id="$1"
|
||||||
|
local body='{"custom_fields":{"update_progress":null}}'
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Cleared update_progress for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to clear update_progress for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- NEW: generic single custom field patch ---
|
||||||
|
nb_patch_custom_field() {
|
||||||
|
local dev_id="$1" key="$2" value="$3"
|
||||||
|
# Build {"custom_fields": { "<key>": "<value>" }}
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg k "$key" --arg v "$value" '{custom_fields: {($k): $v}}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Updated device id=$dev_id custom_field '$key'='$value' OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set custom_field '$key' for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle one JSON payload (object)
|
||||||
|
handle_payload() {
|
||||||
|
local payload="$1"
|
||||||
|
|
||||||
|
local device task task_result task_result_typo add1 notify
|
||||||
|
device="$(jq -r '.inscope_device // empty' <<<"$payload")"
|
||||||
|
task="$(jq -r '.task_name // empty' <<<"$payload")"
|
||||||
|
task_result="$(jq -r '.task_result // empty' <<<"$payload")"
|
||||||
|
task_result_typo="$(jq -r '.taks_result // empty' <<<"$payload")" # backward-compat typo
|
||||||
|
add1="$(jq -r '.task_add1 // empty' <<<"$payload")"
|
||||||
|
notify="$(jq -r '.task_notify // empty' <<<"$payload")" # notify path
|
||||||
|
|
||||||
|
[[ -z "$task_result" && -n "$task_result_typo" ]] && task_result="$task_result_typo"
|
||||||
|
|
||||||
|
if [[ -z "$device" || -z "$task" ]]; then
|
||||||
|
warn "Skipping message (missing inscope_device/task_name)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local dev_id
|
||||||
|
dev_id="$(nb_find_device_id "$device")"
|
||||||
|
if [[ -z "$dev_id" ]]; then
|
||||||
|
err "Device '$device' not found in NetBox"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$task" in
|
||||||
|
update_wo_restart)
|
||||||
|
# If task_notify is present, set multiple_ssids=yes AND add journal with notify text
|
||||||
|
if [[ -n "$notify" ]]; then
|
||||||
|
if nb_set_multiple_ssids_yes "$dev_id"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " multiple_ssids = 'yes'"
|
||||||
|
fi
|
||||||
|
if nb_add_journal_raw "$dev_id" "$notify"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) journal:"
|
||||||
|
echo " added comment = '$notify'"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# existing behavior
|
||||||
|
if nb_patch_update_wo_restart "$dev_id" "$task_result" "$add1"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " update_progress = '$task_result'"
|
||||||
|
echo " updating_to = '$add1'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
deploy_wifidebug)
|
||||||
|
if nb_patch_wifidebug "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " wifidebug = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
journal_add)
|
||||||
|
if nb_add_journal "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) journal:"
|
||||||
|
echo " added comment = 'updated: $task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
tag_add)
|
||||||
|
# add a tag to the device using task_result as slug
|
||||||
|
if [[ -z "$task_result" ]]; then
|
||||||
|
warn "tag_add: task_result (tag slug) is empty; skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if nb_add_tag_by_slug "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " tag added (slug) = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
update_cleanup_success)
|
||||||
|
# cleanup after successful update
|
||||||
|
nb_remove_tag_by_slug "$dev_id" "update-in-progress" || true
|
||||||
|
nb_remove_tag_by_slug "$dev_id" "update-auto-restarted" || true
|
||||||
|
nb_add_tag_by_slug "$dev_id" "update-successful" || true
|
||||||
|
nb_clear_update_progress "$dev_id" || true
|
||||||
|
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) cleanup done:"
|
||||||
|
echo " - tag removed: update-in-progress"
|
||||||
|
echo " - tag removed: update-auto-restarted"
|
||||||
|
echo " - tag added: update-successful"
|
||||||
|
echo " - custom field cleared: update_progress"
|
||||||
|
;;
|
||||||
|
custom_field_set)
|
||||||
|
# NEW: generic setter
|
||||||
|
# task_add1 = field key (e.g., Multissidfix), task_result = value (e.g., yes)
|
||||||
|
if [[ -z "$add1" ]]; then
|
||||||
|
warn "custom_field_set: task_add1 (field key) is empty; skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if nb_patch_custom_field "$dev_id" "$add1" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " custom_fields.$add1 = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
tag_remove)
|
||||||
|
# remove a tag from the device using task_result as slug
|
||||||
|
if [[ -z "$task_result" ]]; then
|
||||||
|
warn "tag_remove: task_result (tag slug) is empty; skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if nb_remove_tag_by_slug "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " tag removed (slug) = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Ignoring unsupported task_name='$task' (no-op)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main consume loop ---
|
||||||
|
log "Consuming from queue='${QUEUE}' (vhost='${VHOST}')"
|
||||||
|
while :; do
|
||||||
|
RESP="$(rmq_api POST "/api/queues/$(urlenc "$VHOST")/$(urlenc "$QUEUE")/get" '{
|
||||||
|
"count": 1, "ackmode": "ack_requeue_false", "encoding": "auto", "truncate": 1000000
|
||||||
|
}')"
|
||||||
|
|
||||||
|
# No messages -> pause
|
||||||
|
if [[ -z "$RESP" || "$RESP" == "[]" ]]; then
|
||||||
|
sleep "$SLEEP_SECS"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process each message (API returns an array)
|
||||||
|
echo "$RESP" | jq -c '.[]' | while read -r item; do
|
||||||
|
raw_payload="$(jq -r '.payload' <<<"$item")"
|
||||||
|
|
||||||
|
# Payload might be a JSON object OR a JSON-encoded string; handle both.
|
||||||
|
if jq -e . >/dev/null 2>&1 <<<"$raw_payload"; then
|
||||||
|
# If it's a string that contains JSON, decode once
|
||||||
|
if [[ "$(jq -r 'type' <<<"$raw_payload")" == "string" ]] && jq -e . >/dev/null 2>&1 <<<"$(jq -r . <<<"$raw_payload")"; then
|
||||||
|
decoded="$(jq -r . <<<"$raw_payload")"
|
||||||
|
handle_payload "$decoded"
|
||||||
|
else
|
||||||
|
handle_payload "$raw_payload"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Skipping non-JSON payload"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
443
files/netbox-reporter.sh-backup
Normal file
443
files/netbox-reporter.sh-backup
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Minimal deps check ---
|
||||||
|
for bin in curl jq; do
|
||||||
|
command -v "$bin" >/dev/null 2>&1 || { echo "[ERROR] Missing dependency: $bin" >&2; exit 2; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- RabbitMQ config (override via env) ---
|
||||||
|
RMQ_USER="${RMQ_USER:-admin}"
|
||||||
|
RMQ_PASS="${RMQ_PASS:-change_me}"
|
||||||
|
RMQ_HOST="${RMQ_HOST:-localhost}"
|
||||||
|
RMQ_PORT="${RMQ_PORT:-15672}"
|
||||||
|
VHOST="${VHOST:-app}"
|
||||||
|
QUEUE="${QUEUE:-queue_controls}"
|
||||||
|
SLEEP_SECS="${SLEEP_SECS:-2}"
|
||||||
|
|
||||||
|
# --- NetBox config (override via env) ---
|
||||||
|
NB_URL="${NB_URL:-http://netbox.gt-tiso.ikeja.co.za}"
|
||||||
|
NB_TOKEN="${NB_TOKEN:-7648e4f5ee370cda7834682e61b47c2ee8e95623}"
|
||||||
|
|
||||||
|
log() { echo "[netbox-reporter] $*"; }
|
||||||
|
warn(){ echo "[netbox-reporter][WARN] $*" >&2; }
|
||||||
|
err() { echo "[netbox-reporter][ERROR] $*" >&2; }
|
||||||
|
|
||||||
|
# URL-encode for RabbitMQ HTTP API paths
|
||||||
|
urlenc() { printf '%s' "$1" | sed -e 's, ,%20,g' -e 's,/,%2F,g'; }
|
||||||
|
|
||||||
|
# Simple RabbitMQ HTTP call
|
||||||
|
rmq_api() {
|
||||||
|
local method="$1" path="$2" data="${3:-}"
|
||||||
|
local url="http://${RMQ_HOST}:${RMQ_PORT}${path}"
|
||||||
|
if [[ -n "$data" ]]; then
|
||||||
|
curl -sS -u "$RMQ_USER:$RMQ_PASS" -H "content-type:application/json" -X "$method" "$url" -d "$data"
|
||||||
|
else
|
||||||
|
curl -sS -u "$RMQ_USER:$RMQ_PASS" -H "content-type:application/json" -X "$method" "$url"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find NetBox device ID by exact name
|
||||||
|
nb_find_device_id() {
|
||||||
|
local name="$1"
|
||||||
|
local resp
|
||||||
|
resp="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/?name=$(printf '%s' "$name" | jq -sRr @uri)&limit=1")" || return 1
|
||||||
|
jq -r '.results[0].id // empty' <<<"$resp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH NetBox custom fields (update_wo_restart)
|
||||||
|
nb_patch_update_wo_restart() {
|
||||||
|
local dev_id="$1" update_progress="$2" updating_to="$3"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg p "$update_progress" --arg u "$updating_to" \
|
||||||
|
'{custom_fields: {update_progress: $p, updating_to: $u}}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Updated device id=$dev_id custom_fields OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to update device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH NetBox custom field: wifidebug (deploy_wifidebug)
|
||||||
|
nb_patch_wifidebug() {
|
||||||
|
local dev_id="$1" value="$2"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg v "$value" '{custom_fields: {wifidebug: $v}}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Updated device id=$dev_id wifidebug='$value' OK"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set wifidebug for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST a Journal Entry on the device (journal_add) with prefix "updated: "
|
||||||
|
nb_add_journal() {
|
||||||
|
local dev_id="$1" text="$2"
|
||||||
|
local comment="updated: ${text}"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg c "$comment" --argjson oid "$dev_id" \
|
||||||
|
'{assigned_object_type:"dcim.device", assigned_object_id:$oid, comments:$c}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/journal-entries/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ || "$code" == "201" ]]; then
|
||||||
|
log "Journal added for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to add journal for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# POST a Journal Entry with raw text (no prefix) — used for task_notify path
|
||||||
|
nb_add_journal_raw() {
|
||||||
|
local dev_id="$1" text="$2"
|
||||||
|
local body
|
||||||
|
body="$(jq -n --arg c "$text" --argjson oid "$dev_id" \
|
||||||
|
'{assigned_object_type:"dcim.device", assigned_object_id:$oid, comments:$c}')"
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/journal-entries/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ || "$code" == "201" ]]; then
|
||||||
|
log "Journal (raw) added for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to add raw journal for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# PATCH custom field: multiple_ssids = "yes" — used for task_notify path
|
||||||
|
nb_set_multiple_ssids_yes() {
|
||||||
|
local dev_id="$1"
|
||||||
|
local body='{"custom_fields":{"multiple_ssids":"yes"}}'
|
||||||
|
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Set multiple_ssids='yes' for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set multiple_ssids for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- NEW: Add a tag to a device using tag slug (ensures tag exists; merges dictionaries) ---
|
||||||
|
nb_add_tag_by_slug() {
|
||||||
|
local dev_id="$1" tag_slug="$2"
|
||||||
|
|
||||||
|
# 1) Lookup tag by slug
|
||||||
|
local tag_resp tag_id tag_name
|
||||||
|
tag_resp="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/extras/tags/?slug=$(printf '%s' "$tag_slug" | jq -sRr @uri)&limit=1")" || tag_resp='{}'
|
||||||
|
tag_id="$(jq -r '.results[0].id // empty' <<<"$tag_resp")"
|
||||||
|
tag_name="$(jq -r '.results[0].name // empty' <<<"$tag_resp")"
|
||||||
|
|
||||||
|
# 2) Create tag if missing
|
||||||
|
if [[ -z "$tag_id" ]]; then
|
||||||
|
local derived_name create_body create_tmp create_code
|
||||||
|
derived_name="$(printf '%s' "$tag_slug" | sed -E 's/-+/ /g')"
|
||||||
|
create_body="$(jq -n --arg name "$derived_name" --arg slug "$tag_slug" '{name:$name, slug:$slug}')"
|
||||||
|
create_tmp="$(mktemp)"
|
||||||
|
create_code="$(curl -sS -o "$create_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST "$NB_URL/api/extras/tags/" \
|
||||||
|
-d "$create_body" || true)"
|
||||||
|
if [[ "$create_code" != "201" && ! "$create_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
err "Failed to create tag slug='$tag_slug' (HTTP $create_code)"
|
||||||
|
cat "$create_tmp" >&2 || true
|
||||||
|
rm -f "$create_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
tag_name="$(jq -r '.name' < "$create_tmp")"
|
||||||
|
rm -f "$create_tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3) Read existing device tags, normalize to [{name, slug}]
|
||||||
|
local dev_detail existing_objs_json
|
||||||
|
dev_detail="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/$dev_id/")"
|
||||||
|
existing_objs_json="$(
|
||||||
|
jq -c '
|
||||||
|
(.tags // []) as $t |
|
||||||
|
if ( ($t|length)>0 and (($t[0]|type)=="object") ) then
|
||||||
|
[ $t[] | {name:.name, slug:.slug} ]
|
||||||
|
else
|
||||||
|
[ $t[] | {name:.} ]
|
||||||
|
end
|
||||||
|
' <<<"$dev_detail"
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 4) Merge + dedupe; build PATCH body
|
||||||
|
local merged_objs_json patch_body patch_tmp patch_code
|
||||||
|
merged_objs_json="$(
|
||||||
|
jq -cn --arg name "$tag_name" --arg slug "$tag_slug" --argjson existing "$existing_objs_json" '
|
||||||
|
($existing + [ {name:$name, slug:$slug} ])
|
||||||
|
| group_by(.name)
|
||||||
|
| map(.[0])
|
||||||
|
'
|
||||||
|
)"
|
||||||
|
patch_body="$(jq -n --argjson tags "$merged_objs_json" '{tags:$tags}')"
|
||||||
|
|
||||||
|
# 5) PATCH device
|
||||||
|
patch_tmp="$(mktemp)"
|
||||||
|
patch_code="$(curl -sS -o "$patch_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$patch_body" || true)"
|
||||||
|
|
||||||
|
if [[ "$patch_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Tag '$tag_slug' applied to device id=$dev_id"
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to set tag '$tag_slug' for device id=$dev_id (HTTP $patch_code)"
|
||||||
|
cat "$patch_tmp" >&2 || true
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- NEW: remove a tag from a device by slug (idempotent) ---
|
||||||
|
nb_remove_tag_by_slug() {
|
||||||
|
local dev_id="$1" tag_slug="$2"
|
||||||
|
|
||||||
|
# 1) Get current tags
|
||||||
|
local dev_detail existing_objs_json
|
||||||
|
dev_detail="$(curl -sS \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$NB_URL/api/dcim/devices/$dev_id/")" || return 1
|
||||||
|
|
||||||
|
existing_objs_json="$(
|
||||||
|
jq -c '
|
||||||
|
(.tags // []) as $t |
|
||||||
|
if ( ($t|length)>0 and (($t[0]|type)=="object") ) then
|
||||||
|
[ $t[] | {name:.name, slug:.slug} ]
|
||||||
|
else
|
||||||
|
[ $t[] | {name:.} ]
|
||||||
|
end
|
||||||
|
' <<<"$dev_detail"
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 2) Filter out the slug (match either by slug or by legacy name)
|
||||||
|
local filtered_objs_json
|
||||||
|
filtered_objs_json="$(
|
||||||
|
jq -cn --arg slug "$tag_slug" --argjson existing "$existing_objs_json" '
|
||||||
|
[ $existing[] | select((.slug // "") != $slug and (.name // "") != $slug) ]
|
||||||
|
'
|
||||||
|
)"
|
||||||
|
|
||||||
|
# 3) PATCH back
|
||||||
|
local patch_body patch_tmp patch_code
|
||||||
|
patch_body="$(jq -n --argjson tags "$filtered_objs_json" '{tags:$tags}')"
|
||||||
|
|
||||||
|
patch_tmp="$(mktemp)"
|
||||||
|
patch_code="$(curl -sS -o "$patch_tmp" -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$patch_body" || true)"
|
||||||
|
|
||||||
|
if [[ "$patch_code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Removed tag '$tag_slug' from device id=$dev_id (if present)"
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to remove tag '$tag_slug' for device id=$dev_id (HTTP $patch_code)"
|
||||||
|
cat "$patch_tmp" >&2 || true
|
||||||
|
rm -f "$patch_tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- NEW: clear only custom_field update_progress (set to null) ---
|
||||||
|
nb_clear_update_progress() {
|
||||||
|
local dev_id="$1"
|
||||||
|
local body='{"custom_fields":{"update_progress":null}}'
|
||||||
|
local code
|
||||||
|
code="$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Token $NB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X PATCH "$NB_URL/api/dcim/devices/$dev_id/" \
|
||||||
|
-d "$body")" || code="000"
|
||||||
|
if [[ "$code" =~ ^20[0-9]$ ]]; then
|
||||||
|
log "Cleared update_progress for device id=$dev_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
err "Failed to clear update_progress for device id=$dev_id (HTTP $code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle one JSON payload (object)
|
||||||
|
handle_payload() {
|
||||||
|
local payload="$1"
|
||||||
|
|
||||||
|
local device task task_result task_result_typo add1 notify
|
||||||
|
device="$(jq -r '.inscope_device // empty' <<<"$payload")"
|
||||||
|
task="$(jq -r '.task_name // empty' <<<"$payload")"
|
||||||
|
task_result="$(jq -r '.task_result // empty' <<<"$payload")"
|
||||||
|
task_result_typo="$(jq -r '.taks_result // empty' <<<"$payload")" # backward-compat typo
|
||||||
|
add1="$(jq -r '.task_add1 // empty' <<<"$payload")"
|
||||||
|
notify="$(jq -r '.task_notify // empty' <<<"$payload")" # NEW: notify path
|
||||||
|
|
||||||
|
[[ -z "$task_result" && -n "$task_result_typo" ]] && task_result="$task_result_typo"
|
||||||
|
|
||||||
|
if [[ -z "$device" || -z "$task" ]]; then
|
||||||
|
warn "Skipping message (missing inscope_device/task_name)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local dev_id
|
||||||
|
dev_id="$(nb_find_device_id "$device")"
|
||||||
|
if [[ -z "$dev_id" ]]; then
|
||||||
|
err "Device '$device' not found in NetBox"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$task" in
|
||||||
|
update_wo_restart)
|
||||||
|
# NEW BEHAVIOR: if task_notify is present, set multiple_ssids=yes AND add journal with notify text
|
||||||
|
if [[ -n "$notify" ]]; then
|
||||||
|
if nb_set_multiple_ssids_yes "$dev_id"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " multiple_ssids = 'yes'"
|
||||||
|
fi
|
||||||
|
if nb_add_journal_raw "$dev_id" "$notify"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) journal:"
|
||||||
|
echo " added comment = '$notify'"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# existing behavior (unchanged)
|
||||||
|
if nb_patch_update_wo_restart "$dev_id" "$task_result" "$add1"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " update_progress = '$task_result'"
|
||||||
|
echo " updating_to = '$add1'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
deploy_wifidebug)
|
||||||
|
if nb_patch_wifidebug "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " wifidebug = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
journal_add)
|
||||||
|
if nb_add_journal "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) journal:"
|
||||||
|
echo " added comment = 'updated: $task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
tag_add)
|
||||||
|
# NEW: add a tag to the device using task_result as slug
|
||||||
|
if [[ -z "$task_result" ]]; then
|
||||||
|
warn "tag_add: task_result (tag slug) is empty; skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if nb_add_tag_by_slug "$dev_id" "$task_result"; then
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) updated:"
|
||||||
|
echo " tag added (slug) = '$task_result'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
# --- NEW ACTION: cleanup on successful update ---
|
||||||
|
update_cleanup_success)
|
||||||
|
# a) remove "update-in-progress"
|
||||||
|
nb_remove_tag_by_slug "$dev_id" "update-in-progress" || true
|
||||||
|
# b) remove "update-auto-restarted"
|
||||||
|
nb_remove_tag_by_slug "$dev_id" "update-auto-restarted" || true
|
||||||
|
# c) add "update-successful"
|
||||||
|
nb_add_tag_by_slug "$dev_id" "update-successful" || true
|
||||||
|
# d) clear custom field update_progress
|
||||||
|
nb_clear_update_progress "$dev_id" || true
|
||||||
|
|
||||||
|
echo ">>> Device '$device' (id=$dev_id) cleanup done:"
|
||||||
|
echo " - tag removed: update-in-progress"
|
||||||
|
echo " - tag removed: update-auto-restarted"
|
||||||
|
echo " - tag added: update-successful"
|
||||||
|
echo " - custom field cleared: update_progress"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Ignoring unsupported task_name='$task' (no-op)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main consume loop ---
|
||||||
|
log "Consuming from queue='${QUEUE}' (vhost='${VHOST}')"
|
||||||
|
while :; do
|
||||||
|
RESP="$(rmq_api POST "/api/queues/$(urlenc "$VHOST")/$(urlenc "$QUEUE")/get" '{
|
||||||
|
"count": 1, "ackmode": "ack_requeue_false", "encoding": "auto", "truncate": 1000000
|
||||||
|
}')"
|
||||||
|
|
||||||
|
# No messages -> pause
|
||||||
|
if [[ -z "$RESP" || "$RESP" == "[]" ]]; then
|
||||||
|
sleep "$SLEEP_SECS"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process each message (API returns an array)
|
||||||
|
echo "$RESP" | jq -c '.[]' | while read -r item; do
|
||||||
|
raw_payload="$(jq -r '.payload' <<<"$item")"
|
||||||
|
|
||||||
|
# Payload might be a JSON object OR a JSON-encoded string; handle both.
|
||||||
|
if jq -e . >/dev/null 2>&1 <<<"$raw_payload"; then
|
||||||
|
# If it's a string that contains JSON, decode once
|
||||||
|
if [[ "$(jq -r 'type' <<<"$raw_payload")" == "string" ]] && jq -e . >/dev/null 2>&1 <<<"$(jq -r . <<<"$raw_payload")"; then
|
||||||
|
decoded="$(jq -r . <<<"$raw_payload")"
|
||||||
|
handle_payload "$decoded"
|
||||||
|
else
|
||||||
|
handle_payload "$raw_payload"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Skipping non-JSON payload"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user