diff --git a/install-graylog.sh b/install-graylog.sh index 7c996f0..501ad54 100644 --- a/install-graylog.sh +++ b/install-graylog.sh @@ -498,39 +498,71 @@ step_alerts() { # Same "compare content, PUT-update if changed" fix as step_pipeline_rules()/ # step_pipelines() - an alert's title never changes when its query/threshold # does, so a plain title-exists check would silently skip the update - # forever. Confirmed live this session: alert7's query was fixed in git - # (vendor:accel-ppp -> vendor:accel-ppp AND event_type:*) but a re-run - # kept reporting "already exists" with the old, buggy query still live. - local existing_defs - existing_defs="$(gcurl GET /events/definitions)" - local f title id body current_config desired_config + # forever. + # + # Uses files instead of interpolating JSON into python -c strings (fragile + # with quotes/backslashes), and compares only the keys OUR file authors in + # "config" - not the whole object. Confirmed live this is necessary: + # Graylog's GET response fills in extra defaults (query_parameters, filters, + # use_cron_scheduling, cron_expression, cron_timezone, ...) that never + # appear in our alert JSON files, so a naive whole-object comparison always + # reports "changed" and PUTs every single alert on every single run, not + # just the one actually edited. + local existing_defs_file + existing_defs_file="$(mktemp)" + gcurl GET /events/definitions > "$existing_defs_file" + + local f title id body_file for f in "$SCRIPT_DIR"/alerts/alert*.json; do [ -f "$f" ] || continue title="$(python3 -c "import json;print(json.load(open('$f'))['title'])")" - body="$(sed \ + body_file="$(mktemp)" + sed \ -e "s/__NETWORK_STREAM_ID__/$NETWORK_STREAM_ID/" \ -e "s/__SERVERS_STREAM_ID__/$SERVERS_STREAM_ID/" \ -e "s/__DISCORD_NOTIFICATION_ID__/$notif_id/" \ - "$f")" - id="$(echo "$existing_defs" | python3 -c "import json,sys;d=json.load(sys.stdin);print(next((e['id'] for e in d['event_definitions'] if e['title']=='$title'),''))")" + "$f" > "$body_file" + + id="$(python3 -c " +import json +d = json.load(open('$existing_defs_file')) +print(next((e['id'] for e in d['event_definitions'] if e['title'] == '$title'), '')) +")" + if [ -n "$id" ]; then - current_config="$(echo "$existing_defs" | python3 -c "import json,sys;d=json.load(sys.stdin);print(json.dumps(next(e['config'] for e in d['event_definitions'] if e['id']=='$id'),sort_keys=True))")" - desired_config="$(echo "$body" | python3 -c "import json,sys;print(json.dumps(json.load(sys.stdin)['config'],sort_keys=True))")" - if [ "$current_config" = "$desired_config" ]; then + if python3 -c " +import json, sys +existing = json.load(open('$existing_defs_file')) +current_full = next(e['config'] for e in existing['event_definitions'] if e['id'] == '$id') +desired = json.load(open('$body_file'))['config'] +filtered_current = {k: current_full.get(k) for k in desired} +sys.exit(0 if filtered_current == desired else 1) +"; then skip "alert '$title' already up to date ($id)" + rm -f "$body_file" continue fi - gcurl PUT "/events/definitions/$id" "$(echo "$body" | python3 -c "import json,sys;d=json.load(sys.stdin);d['id']='$id';print(json.dumps(d))")" >/dev/null + python3 -c " +import json +d = json.load(open('$body_file')) +d['id'] = '$id' +print(json.dumps(d)) +" > "${body_file}.put" + gcurl PUT "/events/definitions/$id" "$(cat "${body_file}.put")" >/dev/null gcurl PUT "/events/definitions/$id/schedule" "" >/dev/null ok "updated alert '$title' ($id) - config changed" + rm -f "$body_file" "${body_file}.put" continue fi - id="$(gcurl POST /events/definitions "{\"entity\": ${body}, \"share_request\": {\"selected_grantee_capabilities\": {}}}" \ + + id="$(gcurl POST /events/definitions "{\"entity\": $(cat "$body_file"), \"share_request\": {\"selected_grantee_capabilities\": {}}}" \ | python3 -c "import json,sys;print(json.load(sys.stdin).get('id',''))")" [ -n "$id" ] || die "alert '$title' failed to create" gcurl PUT "/events/definitions/$id/schedule" "" >/dev/null ok "created and enabled alert '$title' ($id)" + rm -f "$body_file" done + rm -f "$existing_defs_file" } # ---------------------------------------------------------------------------