Files
sicabe-core/deploy/scripts/verify-minio.sh
T
2026-10-13 07:07:05 +07:00

263 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# eBdesk SiCABE — Verifikasi MinIO/S3: memastikan S3_ENDPOINT (url) di config
# BENAR & bisa dipakai — dibuktikan dengan listing object nyata (contoh ≤5).
#
# TANPA mc: seluruh akses S3 memakai Python stdlib (SigV4 + urllib).
#
# Yang diperiksa:
# A. Konfigurasi & endpoint : S3_ENDPOINT, endpoint host-facing, bucket, region, kredensial.
# B. Kesehatan : GET /minio/health/live (dari host).
# C. Akses S3 : ListBuckets (uji ENDPOINT + KREDENSIAL, SigV4).
# D. Bucket & object : bucket ada? + contoh ≤5 object (Key+Size) + jumlah (maks 1000).
# E. Ringkasan
#
# Pemakaian:
# ./scripts/verify-minio.sh
# ./scripts/verify-minio.sh --env-file generated/host-state/.env
# S3_ENDPOINT=https://minio.corp:9000 S3_INSECURE=1 ./scripts/verify-minio.sh
# Dukungan self-signed: set S3_INSECURE=1 (lewati verifikasi TLS).
# Keluar: 0 = OK (WARN boleh) · 1 = ada FAIL · 2 = argumen salah
# =============================================================================
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "${SCRIPT_DIR}/lib/common.sh" 2>/dev/null || true
# shellcheck source=../config/environment.defaults.sh
source "${SCRIPT_DIR}/config/environment.defaults.sh" 2>/dev/null || true
sicabe_load_env "${SCRIPT_DIR}" "$@" || exit 2
usage() { awk 'NR>1 && /^#/ {sub(/^# ?/,""); print; next} NR>1 {exit}' "$0"; }
while [[ $# -gt 0 ]]; do
case "$1" in
--env-file) shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Argumen tidak dikenal: $1" >&2; exit 2 ;;
esac
done
BOLD=$'\e[1m'; GREEN=$'\e[32m'; RED=$'\e[31m'; YELLOW=$'\e[33m'; CYAN=$'\e[36m'; NC=$'\e[0m'
PASSC="${GREEN}[PASS]${NC}"; FAILC="${RED}[FAIL]${NC}"; WARNC="${YELLOW}[WARN]${NC}"; INFOC="${CYAN}[INFO]${NC}"
PASS_N=0; FAIL_N=0; WARN_N=0
pass() { echo -e " ${PASSC} $1"; PASS_N=$((PASS_N + 1)); }
fail() { echo -e " ${FAILC} $1"; FAIL_N=$((FAIL_N + 1)); }
warn() { echo -e " ${WARNC} $1"; WARN_N=$((WARN_N + 1)); }
info() { echo -e " ${INFOC} $1"; }
# ── Konfigurasi (S3_ENDPOINT dilihat CONTAINER; skrip ini di HOST) ───────────
S3_ENDPOINT="${S3_ENDPOINT:-http://minio:9000}"
S3_REGION="${S3_REGION:-us-east-1}"
S3_ACCESS_KEY="${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}}"
S3_SECRET_KEY="${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-}}"
BUCKET="${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}"
# Endpoint HOST-facing: pakai S3_ENDPOINT bila menunjuk ALAMAT (IP/FQDN);
# selain itu (nama service `minio`) → <scheme>://localhost:<MINIO_PORT> (port terbit).
SCHEME="${S3_ENDPOINT%%://*}"
_s3host="${S3_ENDPOINT#*://}"; _s3host="${_s3host%%/*}"; _s3host="${_s3host%%:*}"
if [[ "${_s3host}" == *.* ]]; then
S3_HOST_EP="${S3_ENDPOINT%/}"
elif [[ "${MINIO_HOST:-}" == *.* ]]; then
S3_HOST_EP="${SCHEME}://${MINIO_HOST}:${MINIO_PORT:-12900}"
else
S3_HOST_EP="${SCHEME}://localhost:${MINIO_PORT:-12900}"
fi
# ── Python stdlib: SigV4 + S3 (health / listbuckets / listobjects) ───────────
_PY="$(mktemp)"; trap 'rm -f "${_PY}"' EXIT
cat > "${_PY}" <<'PYEOF'
import os, sys, re, hashlib, hmac, datetime, ssl, urllib.request, urllib.parse, urllib.error
mode = os.environ.get("MODE", "")
ep = (os.environ.get("S3E") or "").rstrip("/")
ak = os.environ.get("S3U") or ""
sk = os.environ.get("S3P") or ""
region = os.environ.get("S3R") or "us-east-1"
bucket = os.environ.get("BUCKET") or ""
insecure = os.environ.get("S3_INSECURE") == "1"
u = urllib.parse.urlparse(ep)
scheme = u.scheme or "http"
host = u.netloc
base_path = u.path.rstrip("/")
def ctx():
return ssl._create_unverified_context() if insecure else None
def tags(xml, tag):
return re.findall(rf"<{tag}>(.*?)</{tag}>", xml, re.S)
def get(path, query=None):
query = query or []
t = datetime.datetime.now(datetime.timezone.utc)
amzdate = t.strftime("%Y%m%dT%H%M%SZ"); datestamp = t.strftime("%Y%m%d")
curi = path or "/"
cqs = "&".join(f"{urllib.parse.quote(k, safe='-_.~')}={urllib.parse.quote(str(v), safe='-_.~')}"
for k, v in sorted(query))
phash = hashlib.sha256(b"").hexdigest()
ch = f"host:{host}\nx-amz-content-sha256:{phash}\nx-amz-date:{amzdate}\n"
sh = "host;x-amz-content-sha256;x-amz-date"
cr = "\n".join(["GET", curi, cqs, ch, sh, phash])
scope = f"{datestamp}/{region}/s3/aws4_request"
sts = "\n".join(["AWS4-HMAC-SHA256", amzdate, scope, hashlib.sha256(cr.encode()).hexdigest()])
def hm(k, m): return hmac.new(k, m.encode(), hashlib.sha256).digest()
kk = hm(("AWS4" + sk).encode(), datestamp); kk = hm(kk, region); kk = hm(kk, "s3"); kk = hm(kk, "aws4_request")
sig = hmac.new(kk, sts.encode(), hashlib.sha256).hexdigest()
auth = f"AWS4-HMAC-SHA256 Credential={ak}/{scope}, SignedHeaders={sh}, Signature={sig}"
url = f"{scheme}://{host}{curi}" + (f"?{cqs}" if cqs else "")
req = urllib.request.Request(url, method="GET")
for h, v in (("Host", host), ("x-amz-date", amzdate),
("x-amz-content-sha256", phash), ("Authorization", auth)):
req.add_header(h, v)
try:
with urllib.request.urlopen(req, timeout=15, context=ctx()) as r:
return r.status, r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
return e.code, e.read().decode("utf-8", "replace")
except Exception as e:
return 0, str(e)
def errline(st, body):
c = tags(body, "Code"); m = tags(body, "Message")
code = (c[0] if c else "").replace("|", " ")
msg = (m[0] if m else body[:140]).replace("|", " ").strip()
print(f"ERR|{st}|{code}|{msg}")
if mode == "health":
try:
with urllib.request.urlopen(f"{scheme}://{host}/minio/health/live", timeout=8, context=ctx()) as r:
print(f"HEALTH|{r.status}")
except urllib.error.HTTPError as e:
print(f"HEALTH|{e.code}")
except Exception:
print("HEALTH|000")
elif mode == "buckets":
st, body = get(base_path or "/")
if st == 200:
names = tags(body, "Name")
print(f"BUCKETS|{len(names)}")
for n in names:
print(f"BUCKET|{n}")
else:
errline(st, body)
elif mode == "objects":
maxk = os.environ.get("MAXKEY", "5")
st, body = get(f"{base_path}/{bucket}", [("list-type", "2"), ("max-keys", maxk)])
if st == 404 or "NoSuchBucket" in body:
print("ERR|404|NoSuchBucket|bucket tak ada")
elif st != 200:
errline(st, body)
else:
keys = tags(body, "Key"); sizes = tags(body, "Size")
trunc = "true" if "<IsTruncated>true</IsTruncated>" in body else "false"
print(f"OK|keycount={len(keys)}|truncated={trunc}")
for i, kk in enumerate(keys):
# TAB-separated: key S3 boleh memuat '|' → jangan pakai '|' sebagai pemisah.
print(f"OBJ\t{kk}\t{sizes[i] if i < len(sizes) else ''}")
PYEOF
s3py() { # <mode> [maxkey] → stdout
MODE="$1" MAXKEY="${2:-}" S3E="${S3_HOST_EP}" S3U="${S3_ACCESS_KEY}" S3P="${S3_SECRET_KEY}" \
S3R="${S3_REGION}" BUCKET="${BUCKET}" S3_INSECURE="${S3_INSECURE:-0}" python3 "${_PY}" 2>/dev/null
}
echo -e "${BOLD}Verifikasi MinIO/S3 — bucket '${BUCKET}'${NC}"
echo ""
# ─────────────────────────────────────────────────────────────────────────────
# A. Konfigurasi & endpoint
# ─────────────────────────────────────────────────────────────────────────────
echo -e "${BOLD}A. Konfigurasi & endpoint${NC}"
info "S3_ENDPOINT (container) = ${S3_ENDPOINT}"
info "endpoint (host, dipakai) = ${S3_HOST_EP}"
info "bucket='${BUCKET}' region='${S3_REGION}' akses='${S3_ACCESS_KEY}'"
if [[ -z "${S3_SECRET_KEY}" ]]; then
fail "S3_SECRET_KEY / MINIO_ROOT_PASSWORD kosong — kredensial tak lengkap"
fi
echo ""
# ─────────────────────────────────────────────────────────────────────────────
# B. Kesehatan (host)
# ─────────────────────────────────────────────────────────────────────────────
echo -e "${BOLD}B. Kesehatan${NC}"
hc="$(s3py health)"
hc="${hc#HEALTH|}"
if [[ "${hc}" == "200" ]]; then
pass "MinIO sehat: ${S3_HOST_EP}/minio/health/live (HTTP 200)"
elif [[ "${hc}" == "000" ]]; then
warn "MinIO tak terjangkau dari host (${S3_HOST_EP}) — normal bila S3 eksternal / beda jaringan"
else
warn "Health MinIO HTTP ${hc} di ${S3_HOST_EP} — cek endpoint/port"
fi
echo ""
# ─────────────────────────────────────────────────────────────────────────────
# C. Akses S3 (ListBuckets) — uji ENDPOINT + KREDENSIAL (SigV4)
# ─────────────────────────────────────────────────────────────────────────────
echo -e "${BOLD}C. Akses S3 (ListBuckets)${NC}"
bout="$(s3py buckets)"
if [[ "${bout}" == BUCKETS\|* ]]; then
nb="${bout#BUCKETS|}"; nb="${nb%%$'\n'*}"
pass "ListBuckets OK via ${S3_HOST_EP} — ${nb} bucket"
printf '%s\n' "${bout}" | awk -F'|' '$1=="BUCKET"{printf " • %s\n", $2}'
elif [[ "${bout}" == ERR\|* ]]; then
IFS='|' read -r _ st code msg <<< "${bout}"
hint=""
case "${st}" in
0) hint="tidak terjangkau (koneksi/DNS/TLS) — cek endpoint/port; self-signed? set S3_INSECURE=1";;
403) hint="kredensial/diakses ditolak (cek S3_ACCESS_KEY / S3_SECRET_KEY)";;
401) hint="butuh autentikasi";;
*) hint="${code:-HTTP ${st}}";;
esac
fail "ListBuckets GAGAL [HTTP ${st} — ${hint}]${msg:+ · ${msg}}"
else
fail "ListBuckets tak menghasilkan keluaran yang diharapkan"
fi
echo ""
# ─────────────────────────────────────────────────────────────────────────────
# D. Bucket & object (contoh ≤5)
# ─────────────────────────────────────────────────────────────────────────────
echo -e "${BOLD}D. Bucket & object (contoh ≤5)${NC}"
o5="$(s3py objects 5)"
if [[ "${o5}" == OK\|* ]]; then
pass "bucket '${BUCKET}' ada & bisa dibaca"
meta="${o5%%$'\n'*}"; rest="${meta#*|}"; kc="${rest%%|*}"
if [[ "${kc#keycount=}" -gt 0 ]]; then
pass "menampilkan ≤5 object (bukti S3_ENDPOINT benar)"
printf '%s\n' "${o5}" | awk -F'\t' '$1=="OBJ"{printf " • %s (%s bytes)\n", $2, $3}'
else
warn "bucket ada tetapi KOSONG (0 object) — normal bila belum ada ingest"
fi
# jumlah (maks 1000) — info
o1000="$(s3py objects 1000)"
if [[ "${o1000}" == OK\|* ]]; then
meta="${o1000%%$'\n'*}"; r="${meta#*|}"; k1="${r%%|*}"; tr="${r##*|}"
info "jumlah object (maks 1000): ${k1#keycount=}${tr:+ · ${tr}}"
fi
elif [[ "${o5}" == ERR\|* ]]; then
IFS='|' read -r _ st code msg <<< "${o5}"
if [[ "${st}" == "404" ]]; then
fail "bucket '${BUCKET}' TIDAK ADA [HTTP 404 — ${msg:-NoSuchBucket}]"
info "Buat bucket di sisi pemilik/MinIO (bootstrap step 2 / config/minio/buckets.sh)."
else
hint="cek endpoint/kredensial"
[[ "${st}" == "0" ]] && hint="tidak terjangkau (koneksi/DNS/TLS) — self-signed? set S3_INSECURE=1"
fail "baca bucket '${BUCKET}' GAGAL [HTTP ${st} — ${hint}]${msg:+ · ${msg}}"
fi
else
fail "baca bucket tak menghasilkan keluaran yang diharapkan"
fi
echo ""
# ─────────────────────────────────────────────────────────────────────────────
# E. Ringkasan
# ─────────────────────────────────────────────────────────────────────────────
echo -e "${BOLD}Ringkasan: ${PASS_N} PASS · ${FAIL_N} FAIL · ${WARN_N} WARN${NC}"
if [[ ${FAIL_N} -eq 0 ]]; then
echo -e "${GREEN}✓ S3_ENDPOINT valid & bisa dipakai (bucket '${BUCKET}').${NC}"
exit 0
fi
echo -e "${RED}✗ Ada ${FAIL_N} kegagalan — cek S3_ENDPOINT / kredensial / bucket.${NC}"
exit 1