Skip to content

Commit 79863d3

Browse files
committed
Add helper script to unlock elasticsearch indices after they get set to read-only
1 parent 23141e6 commit 79863d3

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

bin/unlock-es-index

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ------------------------------------------------------------
5+
# unlock-es-index.sh
6+
# Detects ES indices blocked by flood-stage (read_only_allow_delete/read_only)
7+
# and unlocks only those. Defaults to scanning ALL indices.
8+
#
9+
# Requires: jq
10+
# ------------------------------------------------------------
11+
12+
usage() {
13+
cat <<'USAGE'
14+
Usage:
15+
unlock-es-index.sh [-h host] [-p port] [-u user:pass] [--force-all] [index1 index2 ...]
16+
17+
Options:
18+
-h Elasticsearch host (default: localhost or $ES_HOST)
19+
-p Elasticsearch port (default: 9200 or $ES_PORT)
20+
-u Basic auth "user:password" (optional; or use $ES_AUTH)
21+
--force-all Unlock even if not detected as blocked (dangerous)
22+
-v Verbose output
23+
-y Assume yes; do not prompt
24+
25+
Behavior:
26+
• No index args -> scan ALL indices and unlock only those detected as blocked
27+
• With args -> scan only the provided indices and unlock only those blocked
28+
• --force-all -> run unlock on every matching index regardless of detected block
29+
USAGE
30+
exit 1
31+
}
32+
33+
require() {
34+
command -v "$1" >/dev/null 2>&1 || { echo "[ERROR] '$1' is required"; exit 1; }
35+
}
36+
37+
HOST="${ES_HOST:-localhost}"
38+
PORT="${ES_PORT:-9200}"
39+
AUTH="${ES_AUTH:-}"
40+
FORCE_ALL=false
41+
VERBOSE=false
42+
ASSUME_YES=false
43+
44+
# Parse flags
45+
ARGS=()
46+
while (( "$#" )); do
47+
case "${1:-}" in
48+
-h) HOST="$2"; shift 2 ;;
49+
-p) PORT="$2"; shift 2 ;;
50+
-u) AUTH="$2"; shift 2 ;;
51+
--force-all) FORCE_ALL=true; shift ;;
52+
-v) VERBOSE=true; shift ;;
53+
-y) ASSUME_YES=true; shift ;;
54+
-h|--help) usage ;;
55+
--) shift; break ;;
56+
-*)
57+
echo "[ERROR] Unknown option: $1"
58+
usage
59+
;;
60+
*) ARGS+=("$1"); shift ;;
61+
esac
62+
done
63+
64+
# Remaining args are indices (optional)
65+
INDICES=("${ARGS[@]:-}")
66+
67+
require jq
68+
69+
# Build curl args
70+
CURL_OPTS=(-sS)
71+
[[ "$VERBOSE" == "true" ]] && CURL_OPTS=(-v)
72+
[[ -n "$AUTH" ]] && CURL_OPTS+=(-u "$AUTH")
73+
74+
base_url() { echo "http://${HOST}:${PORT}"; }
75+
76+
# Query which indices are blocked
77+
# We consider blocked if either:
78+
# index.blocks.read_only_allow_delete == true
79+
# OR index.blocks.read_only == true
80+
query_blocked_indices() {
81+
local scope
82+
if [ ${#INDICES[@]} -eq 0 ]; then
83+
scope="_all"
84+
else
85+
# comma-separated list
86+
local IFS=','; scope="${INDICES[*]}"
87+
fi
88+
89+
curl "${CURL_OPTS[@]}" "$(base_url)/${scope}/_settings?filter_path=*.settings.index.blocks" \
90+
| jq -r '
91+
to_entries
92+
| map(
93+
select(
94+
(.value.settings.index.blocks.read_only_allow_delete == true)
95+
or (.value.settings.index.blocks.read_only_allow_delete == "true")
96+
or (.value.settings.index.blocks.read_only == true)
97+
or (.value.settings.index.blocks.read_only == "true")
98+
)
99+
)
100+
| .[].key
101+
'
102+
}
103+
104+
unlock_index() {
105+
local index="$1"
106+
local payload='{
107+
"index.blocks.read_only_allow_delete": null,
108+
"index.blocks.read_only": false
109+
}'
110+
111+
local code
112+
code=$(curl "${CURL_OPTS[@]}" -o /dev/null -w "%{http_code}" \
113+
-X PUT "$(base_url)/${index}/_settings" \
114+
-H 'Content-Type: application/json' \
115+
-d "$payload")
116+
117+
if [ "$code" -eq 200 ]; then
118+
echo "[SUCCESS] Unlocked '${index}'."
119+
else
120+
echo "[ERROR] Failed to unlock '${index}'. HTTP ${code}"
121+
fi
122+
}
123+
124+
# Determine target indices
125+
BLOCKED_LIST=()
126+
if "$FORCE_ALL"; then
127+
if [ ${#INDICES[@]} -eq 0 ]; then
128+
# Expand to all indices via _cat/indices
129+
mapfile -t TARGETS < <(curl "${CURL_OPTS[@]}" "$(base_url)/_cat/indices?h=index" | awk '{print $1}')
130+
else
131+
TARGETS=("${INDICES[@]}")
132+
fi
133+
else
134+
mapfile -t TARGETS < <(query_blocked_indices)
135+
fi
136+
137+
if [ ${#TARGETS[@]} -eq 0 ]; then
138+
if "$FORCE_ALL"; then
139+
echo "[INFO] No indices found to target."
140+
else
141+
echo "[INFO] No blocked indices detected. Nothing to do."
142+
fi
143+
exit 0
144+
fi
145+
146+
echo "[INFO] Will attempt to unlock ${#TARGETS[@]} index(es):"
147+
printf ' - %s\n' "${TARGETS[@]}"
148+
149+
if ! "$ASSUME_YES"; then
150+
read -r -p "Proceed? [y/N] " ans
151+
[[ "${ans:-}" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
152+
fi
153+
154+
for idx in "${TARGETS[@]}"; do
155+
echo "[INFO] Unlocking: $idx"
156+
unlock_index "$idx"
157+
done

0 commit comments

Comments
 (0)