Files
sicabe-core/deploy/scripts/verify-phase2-storage.sh
T

439 lines
21 KiB
Bash
Raw Normal View History

2026-10-09 02:35:14 +07:00
#!/usr/bin/env bash
# =============================================================================
# eBdesk SiCABE — Verifikasi Phase 2 (Storage & Catalog): MinIO/S3 + Nessie
#
# Gate "kontrak storage": memastikan object storage & Nessie benar-benar SIAP
# dipakai fase berikutnya (Trino, StarRocks, Airflow/Sling, Dremio) — termasuk
# saat memakai object storage / Nessie EXISTING (S3_ENDPOINT, NESSIE_URL).
#
# Yang diperiksa:
# A. Object storage : endpoint terjangkau, bucket ada, tulis+baca berhasil
2026-10-12 21:10:45 +07:00
# B. Nessie : /api/v2/config, default branch, endpoint Iceberg REST <prefix>/v1/config
# (prefix dari NESSIE_ICEBERG_PREFIX, default /iceberg)
2026-10-09 02:35:14 +07:00
# C. Version store : database `nessie` + tabel Nessie di PostgreSQL (JDBC2)
# D. Kontrak & antarmuka:
# D1 (milik Phase 2) : warehouse & endpoint S3 pada Nessie (compose) → FAIL bila salah
# D2 (milik fase 3/4/5) : config Trino / Sling / StarRocks selaras → WARN/SKIP saja
#
# Mode --deep (opsional — menguji KONSUMEN, jadi komponennya harus sudah ada):
# E. Trino : baca nyata dari warehouse (SELECT count(*) FROM iceberg.bronze.orders)
#
# Prinsip gate: hanya memverifikasi kontrak yang DIMILIKI fase ini. Pemeriksaan yang
# bergantung pada runtime fase lain (mis. Trino di fase 3) tidak dijalankan kecuali --deep,
# supaya gate tidak pernah "hijau karena di-SKIP" atau salah atribusi kegagalan.
#
# Exit code: 0 = semua gate lolos (WARN/SKIP boleh), 1 = ada FAIL.
#
# Usage:
# ./scripts/verify-phase2-storage.sh
# ./scripts/verify-phase2-storage.sh --deep # + uji baca via Trino (fase 3)
# S3_ENDPOINT=https://s3.corp.internal NESSIE_URL=https://nessie.corp.internal \
# ./scripts/verify-phase2-storage.sh
# =============================================================================
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../lib/common.sh
source "${SCRIPT_DIR}/lib/common.sh"
# shellcheck source=../config/environment.defaults.sh
source "${SCRIPT_DIR}/config/environment.defaults.sh"
sicabe_load_env "${SCRIPT_DIR}" "$@"
# Config RELEVAN: mode host (--env-file) = BUNDLE host (ter-render), bukan deploy/config/ sumber.
if [[ -n "${SICABE_BUNDLE_DIR:-}" ]]; then CONFIG_DIR="${SICABE_BUNDLE_DIR}/config"; else CONFIG_DIR="${SCRIPT_DIR}/config"; fi
S3_ENDPOINT="${S3_ENDPOINT:-http://minio:9000}"
2026-10-12 21:10:45 +07:00
S3_SCHEME="${S3_ENDPOINT%%://*}" # http | https — dipakai uji kesehatan host
2026-10-09 02:35:14 +07:00
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}"
2026-10-11 15:09:43 +07:00
WAREHOUSE="${ICEBERG_WAREHOUSE:-warehouse}"
2026-10-09 02:35:14 +07:00
NESSIE_URL="${NESSIE_URL:-http://nessie:19120}"
NESSIE_HOST="${NESSIE_HOST:-localhost}"
NESSIE_PORT="${NESSIE_PORT:-12120}"
2026-10-12 14:26:44 +07:00
# API host-facing. Bila NESSIE_URL menunjuk ALAMAT (IP/FQDN — Nessie EKSTERNAL/lintas-host),
# pakai apa adanya (sudah memuat port benar: generator memakai NESSIE_INTERNAL_PORT, mis.
# 19120). Selebihnya (nama service `nessie:19120` / single-host) → host:port terbit (NESSIE_PORT).
NESSIE_HOST_API="$(sicabe_host_url NESSIE_URL NESSIE_HOST "${NESSIE_PORT:-12120}")"
2026-10-09 02:35:14 +07:00
NESSIE_DB="${NESSIE_DB:-nessie}"
2026-10-12 21:10:45 +07:00
# Prefix REST Iceberg Nessie (mis. `/iceberg`); ubah bila layanan existing memakai prefix lain.
ICEBERG_PREFIX="${NESSIE_ICEBERG_PREFIX:-/iceberg}"
2026-10-09 02:35:14 +07:00
# Mode --deep: ikut menguji KONSUMEN (Trino) — komponennya harus sudah ada (fase 3+).
DEEP=0
while [[ $# -gt 0 ]]; do
case "$1" in
--env-file) shift 2 ;; # sudah dipakai sicabe_load_env() di atas
--deep) DEEP=1; shift ;;
"") break ;;
*) echo "Argumen tidak dikenal: $1 (gunakan --deep)"; exit 1 ;;
esac
done
MC_IMAGE="quay.io/minio/mc:${MINIO_MC_VERSION:-RELEASE.2025-08-13T08-35-41Z}"
TRINO_URL="$(sicabe_local_url https TRINO_HOST "${TRINO_HTTPS_PORT:-12443}" TRINO_URL)"
PASS_N=0
FAIL_N=0
WARN_N=0
SKIP_N=0
pass() { echo -e " ${PASS} $1"; PASS_N=$((PASS_N + 1)); }
FAIL_MSGS=()
fail() { echo -e " ${FAIL} $1"; FAIL_N=$((FAIL_N + 1)); FAIL_MSGS+=("$1"); }
warn() { echo -e " ${WARN} $1"; WARN_N=$((WARN_N + 1)); }
skip() { echo -e " ${YELLOW}[SKIP]${NC} $1"; SKIP_N=$((SKIP_N + 1)); }
2026-10-12 21:10:45 +07:00
# Kode HTTP untuk <url> (000 = koneksi gagal/timeout). Dipakai agar pesan FAIL
# menampilkan PENYEBAB (404/401/500/000), bukan hanya "tidak merespon".
2026-10-12 21:53:26 +07:00
http_code() { local c; c="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$1" 2>/dev/null)"; printf '%s' "${c:-000}"; }
2026-10-12 21:10:45 +07:00
code_hint() {
case "$1" in
000) echo "tidak terjangkau (koneksi/DNS/timeout) — cek port, alamat, jaringan";;
401|403) echo "butuh autentikasi — Nessie existing mungkin memakai bearer token";;
404) echo "path tidak ada — versi Nessie lama / Iceberg REST belum aktif (butuh warehouse+object store) / proxy tak merutekan path ini";;
429) echo "rate-limited oleh proxy/edge";;
500|502|503|504) echo "error di server/proxy — cek konfigurasi Nessie (warehouse/object store) atau proxy";;
*) echo "HTTP $1";;
esac
}
2026-10-09 02:35:14 +07:00
container_running() {
docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^$1$"
}
# ─────────────────────────────────────────────────────────────────────────────
# A. Object storage
# ─────────────────────────────────────────────────────────────────────────────
s3_mc() {
# Menjalankan perintah mc di container (image ter-pin) dengan kredensial S3
# lewat environment, agar tidak muncul di daftar proses.
docker run --rm -i \
--network "${SICABE_NETWORK:-sicabe-network}" \
-e S3E="${S3_ENDPOINT}" -e S3U="${S3_ACCESS_KEY}" -e S3P="${S3_SECRET_KEY}" \
-e BUCKET="${BUCKET}" \
--entrypoint sh "${MC_IMAGE}" -c "$1" 2>&1
}
check_object_storage() {
echo -e "${BOLD}A. Object storage — ${S3_ENDPOINT} (bucket '${BUCKET}')${NC}"
if [[ -z "${S3_SECRET_KEY}" ]]; then
fail "S3_SECRET_KEY / MINIO_ROOT_PASSWORD kosong — kredensial storage tidak lengkap"
return 1
fi
# S3_ENDPOINT umumnya hanya terjangkau dari jaringan Docker (mis.
# http://minio:9000) → uji keterjangkauan dilakukan dari container (uji mc).
# Di host kita cek port publik MinIO saja (informatif, bukan gate).
2026-10-13 21:26:50 +07:00
# Bila S3_ENDPOINT berupa ALAMAT (IP/FQDN, mis. https://minio-api.sicabe.com:8443),
# pakai APA ADANYA — termasuk port-nya — lewat sicabe_host_url. Tanpa ini, URL
# penuh diprobe di port terbit lokal (12900) → WARN palsu.
local host_ep="$(sicabe_host_url S3_ENDPOINT MINIO_HOST "${MINIO_PORT:-12900}" "${S3_SCHEME:-http}")"
2026-10-09 02:35:14 +07:00
local host_code
host_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 8 \
"${host_ep}/minio/health/live" 2>/dev/null || true)
if [[ "${host_code}" == "200" ]]; then
pass "Storage terjangkau dari host: ${host_ep}"
else
2026-10-12 21:10:45 +07:00
warn "Storage tidak terjangkau dari host (${host_ep}) — normal bila memakai S3 eksternal [HTTP ${host_code:-000} — $(code_hint "${host_code:-000}")]"
2026-10-09 02:35:14 +07:00
fi
if ! docker image inspect "${MC_IMAGE}" >/dev/null 2>&1; then
skip "Image mc '${MC_IMAGE}' belum ada lokal — lewati uji bucket tulis/baca"
return 0
fi
local out
out=$(s3_mc 'if ! mc alias set probe "$S3E" "$S3U" "$S3P" >/dev/null 2>&1; then
echo "mc: $(mc alias set probe "$S3E" "$S3U" "$S3P" 2>&1 | tail -2)"
echo ALIAS_FAIL
exit 0
fi
mc ls "probe/$BUCKET" >/dev/null 2>&1 && echo BUCKET_OK || echo BUCKET_MISSING')
if [[ "${out}" == *ALIAS_FAIL* ]]; then
fail "Gagal autentikasi/terjangkau dari container (cek S3_ENDPOINT & kredensial)"
warn " keluaran mc: $(echo "${out}" | tr '\n' ' ' | cut -c1-160)"
return 1
elif [[ "${out}" == *BUCKET_OK* ]]; then
pass "Bucket '${BUCKET}' ada dan bisa diakses dari container"
else
fail "Bucket '${BUCKET}' tidak ada / tidak bisa diakses"
return 1
fi
out=$(s3_mc 'mc alias set probe "$S3E" "$S3U" "$S3P" >/dev/null 2>&1
echo "verify-$RANDOM" > /tmp/probe.txt
if mc cp /tmp/probe.txt "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1; then
mc cat "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1 && echo RW_OK || echo READ_FAIL
mc rm "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1
else
echo WRITE_FAIL
fi')
case "${out}" in
*RW_OK*) pass "Uji tulis+baca objek di bucket '${BUCKET}' berhasil" ;;
*WRITE_FAIL*) fail "Gagal menulis objek ke bucket '${BUCKET}'" ;;
*READ_FAIL*) fail "Gagal membaca kembali objek dari bucket '${BUCKET}'" ;;
*) warn "Hasil uji tulis/baca tidak terbaca: ${out}" ;;
esac
}
# ─────────────────────────────────────────────────────────────────────────────
# B. Nessie
# ─────────────────────────────────────────────────────────────────────────────
check_nessie() {
echo -e "${BOLD}B. Nessie — ${NESSIE_URL} (API host: ${NESSIE_HOST_API})${NC}"
2026-10-12 21:10:45 +07:00
local cfg code
2026-10-09 02:35:14 +07:00
cfg=$(curl -sf --max-time 10 "${NESSIE_HOST_API}/api/v2/config" 2>/dev/null || echo "")
if [[ -z "${cfg}" ]]; then
2026-10-12 21:10:45 +07:00
code="$(http_code "${NESSIE_HOST_API}/api/v2/config")"
fail "Nessie REST /api/v2/config tidak merespon di ${NESSIE_HOST_API} [HTTP ${code} — $(code_hint "${code}")]"
2026-10-09 02:35:14 +07:00
return 1
fi
pass "Nessie REST v2 aktif"
echo "${cfg}" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print(' defaultBranch =', d.get('defaultBranch'))
print(' specVersion =', d.get('specVersion'))" 2>/dev/null || true
local iceberg_cfg
2026-10-12 21:10:45 +07:00
iceberg_cfg=$(curl -sf --max-time 10 "${NESSIE_HOST_API}${ICEBERG_PREFIX}/v1/config" 2>/dev/null || echo "")
2026-10-09 02:35:14 +07:00
if [[ -n "${iceberg_cfg}" ]]; then
2026-10-12 21:10:45 +07:00
pass "Endpoint Iceberg REST (${ICEBERG_PREFIX}) aktif (dipakai Sling & StarRocks)"
2026-10-09 02:35:14 +07:00
else
2026-10-12 21:10:45 +07:00
code="$(http_code "${NESSIE_HOST_API}${ICEBERG_PREFIX}/v1/config")"
fail "Endpoint Iceberg REST ${NESSIE_HOST_API}${ICEBERG_PREFIX} tidak merespon [HTTP ${code} — $(code_hint "${code}")]"
2026-10-09 02:35:14 +07:00
fi
local branch
branch=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${NESSIE_HOST_API}/api/v2/trees/main" 2>/dev/null || echo "000")
if [[ "${branch}" == "200" ]]; then
pass "Branch 'main' tersedia"
else
2026-10-12 21:10:45 +07:00
warn "Branch 'main' tidak merespon 200 [HTTP ${branch} — $(code_hint "${branch}")] — katalog mungkin masih kosong"
2026-10-09 02:35:14 +07:00
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# C. Version store (PostgreSQL)
# ─────────────────────────────────────────────────────────────────────────────
check_version_store() {
echo -e "${BOLD}C. Version store Nessie di PostgreSQL (JDBC2)${NC}"
if ! docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^sicabe-postgres$"; then
skip "Container sicabe-postgres tidak berjalan — lewati cek version store"
return 0
fi
local dbs
dbs=$(docker exec sicabe-postgres psql -U "${POSTGRES_USER:-sicabe_admin}" -d postgres -tAc \
"SELECT datname FROM pg_database;" 2>/dev/null || echo "")
# NB: keluaran psql dipisah baris baru → gunakan grep -x (bukan "* x *").
if grep -qx "${NESSIE_DB}" <<< "${dbs}"; then
pass "Database '${NESSIE_DB}' ada"
else
fail "Database '${NESSIE_DB}' tidak ada (dibuat oleh config/postgres/init/01-create-dbs.sh)"
return 1
fi
local tables
tables=$(docker exec sicabe-postgres psql -U "${NESSIE_DB_USER:-nessie}" -d "${NESSIE_DB}" -tAc \
"SELECT count(*) FROM information_schema.tables WHERE table_schema='nessie';" 2>/dev/null || echo "")
if [[ "${tables}" =~ ^[0-9]+$ ]] && [[ "${tables}" -gt 0 ]]; then
pass "Schema 'nessie' berisi ${tables} tabel (version store aktif)"
else
warn "Schema 'nessie' belum berisi tabel — Nessie mungkin belum pernah start"
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# D1. Kontrak Phase 2 — warehouse & endpoint S3 pada Nessie (compose)
# ─────────────────────────────────────────────────────────────────────────────
check_storage_contract() {
echo -e "${BOLD}D1. Kontrak Phase 2 — warehouse & endpoint S3 Nessie${NC}"
local cfg wh ep
cfg=$(docker compose -f "${SCRIPT_DIR}/docker-compose.yml" --profile all config 2>/dev/null || echo "")
if [[ -z "${cfg}" ]]; then
fail "Tidak bisa membaca konfigurasi compose"
return 1
fi
wh=$(echo "${cfg}" | sed -n 's/.*WAREHOUSE_LOCATION: *//p' | head -1 | tr -d '"')
ep=$(echo "${cfg}" | sed -n 's/.*DEFAULT_OPTIONS_ENDPOINT: *//p' | head -1 | tr -d '"')
2026-10-11 15:09:43 +07:00
if [[ "${wh}" == "s3://${BUCKET}/${WAREHOUSE}" ]]; then
pass "Warehouse Nessie → ${wh} (bucket '${BUCKET}' + warehouse '${WAREHOUSE}')"
2026-10-09 02:35:14 +07:00
elif [[ -n "${wh}" ]]; then
2026-10-11 15:09:43 +07:00
fail "Warehouse Nessie '${wh}' ≠ 's3://${BUCKET}/${WAREHOUSE}' — bucket/warehouse tidak sinkron"
2026-10-09 02:35:14 +07:00
else
fail "WAREHOUSE_LOCATION Nessie tidak ditemukan di compose"
fi
if [[ "${ep}" == "${S3_ENDPOINT}" ]]; then
pass "Endpoint S3 Nessie → ${ep}"
elif [[ -n "${ep}" ]]; then
fail "Endpoint S3 Nessie '${ep}' ≠ S3_ENDPOINT '${S3_ENDPOINT}'"
else
warn "DEFAULT_OPTIONS_ENDPOINT Nessie tidak ditemukan di compose"
fi
pass "Region S3: ${S3_REGION}"
}
# ─────────────────────────────────────────────────────────────────────────────
# D2. Antarmuka konsumen (Trino/Sling/StarRocks) — milik fase 3/4/5 → WARN/SKIP
# ─────────────────────────────────────────────────────────────────────────────
check_consumer_interfaces() {
echo -e "${BOLD}D2. Antarmuka konsumen — Trino / Sling / StarRocks (informatif, bukan gate)${NC}"
if ! sicabe_host_expects_container sicabe-trino-coordinator; then
skip "Container sicabe-trino-coordinator di host lain (mode host) - dilewati"
elif container_running sicabe-trino-coordinator; then
local props="${CONFIG_DIR}/trino/catalog/iceberg.properties"
local prop_bucket prop_endpoint
prop_bucket=$(sed -n "s|.*default-warehouse-dir=s3://\([^/]*\)/.*|\1|p" "${props}" 2>/dev/null)
prop_endpoint=$(sed -n "s|^s3.endpoint=||p" "${props}" 2>/dev/null)
if [[ "${prop_bucket}" == '${ENV:MINIO_BUCKET_ICEBERG_DATA}' || "${prop_bucket}" == "${BUCKET}" ]]; then
pass "Trino iceberg.properties → bucket dari env (uji penuh di gate Phase 3)"
else
warn "Trino iceberg.properties bucket '${prop_bucket:-?}' ≠ '${BUCKET}'"
fi
if [[ "${prop_endpoint}" == '${ENV:S3_ENDPOINT}' || "${prop_endpoint}" == "${S3_ENDPOINT}" ]]; then
pass "Trino iceberg.properties → endpoint dari env"
else
warn "Trino iceberg.properties masih hardcode endpoint: ${prop_endpoint:-?}"
fi
else
skip "Trino belum berjalan (fase 3) — lewati pemeriksaan antarmukanya"
fi
if ! sicabe_host_expects_container sicabe-airflow-webserver; then
skip "Container sicabe-airflow-webserver di host lain (mode host) - dilewati"
elif container_running sicabe-airflow-webserver; then
local sling_file="${CONFIG_DIR}/airflow/sling/env.yaml"
if [[ -f "${sling_file}" ]]; then
local sling_uri sling_ep
sling_uri=$(sed -n 's/^ *rest_uri: *//p' "${sling_file}" | head -1)
sling_ep=$(sed -n 's/^ *s3_endpoint: *//p' "${sling_file}" | head -1)
if [[ "${sling_uri}" == *nessie* ]]; then
pass "Sling env.yaml → rest_uri ${sling_uri}"
else
warn "Sling env.yaml rest_uri tidak menunjuk Nessie: ${sling_uri:-?}"
fi
[[ -n "${sling_ep}" ]] && pass "Sling env.yaml → s3_endpoint ${sling_ep}"
else
skip "config/airflow/sling/env.yaml belum ada (dibuat bootstrap step 6 — fase 5)"
fi
else
skip "Airflow belum berjalan (fase 5) — lewati pemeriksaan antarmuka Sling"
fi
if ! sicabe_host_expects_container sicabe-starrocks-fe; then
skip "Container sicabe-starrocks-fe di host lain (mode host) - dilewati"
elif container_running sicabe-starrocks-fe; then
local sr_ep
sr_ep=$(grep -m1 -oE "'aws\.s3\.endpoint' = '[^']*'" "${SCRIPT_DIR}/bootstrap.sh" 2>/dev/null |
sed "s/.*= '//; s/'//")
if [[ -n "${sr_ep}" ]]; then
pass "StarRocks s3_volume endpoint → ${sr_ep} (uji penuh di gate Phase 4)"
else
warn "Tidak bisa membaca endpoint s3_volume StarRocks dari bootstrap.sh"
fi
else
skip "StarRocks belum berjalan (fase 4) — lewati pemeriksaan antarmukanya"
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# E. Trino — bukti warehouse bisa dibaca
# ─────────────────────────────────────────────────────────────────────────────
check_trino_read() {
echo -e "${BOLD}E. Trino — baca nyata dari warehouse${NC}"
if ! curl -skf --max-time 10 "${TRINO_URL}/v1/info" >/dev/null 2>&1; then
skip "Trino belum siap — lewati uji baca"
return 0
fi
local result
result=$(curl -sk --max-time 20 -X POST "${TRINO_URL}/v1/statement" \
-u "svc_seed:${TRINO_SVC_SEED_PASSWORD}" \
-H "Content-Type: text/plain" \
-d "SELECT count(*) FROM iceberg.bronze.orders" 2>/dev/null || echo "")
if [[ -z "${result}" ]]; then
skip "Query Trino tidak menghasilkan respon (service account/permission)"
return 0
fi
# Trino mengembalikan status QUEUED + nextUri → perlu polling sampai FINISHED.
local state next tries=0
while [[ ${tries} -lt 15 ]]; do
state=$(echo "${result}" | python3 -c "
import sys,json
try: print(json.load(sys.stdin).get('stats',{}).get('state',''))
except Exception: print('')" 2>/dev/null)
[[ "${state}" == "FINISHED" || "${state}" == "FAILED" ]] && break
next=$(echo "${result}" | python3 -c "
import sys,json
try: print(json.load(sys.stdin).get('nextUri',''))
except Exception: print('')" 2>/dev/null)
[[ -z "${next}" ]] && break
sleep 1
result=$(curl -sk --max-time 20 "${next}" 2>/dev/null || echo "")
tries=$((tries + 1))
done
if [[ "${state}" == "FINISHED" ]]; then
pass "Query 'SELECT count(*) FROM iceberg.bronze.orders' FINISHED (warehouse terbaca)"
elif [[ "${state}" == "FAILED" ]]; then
warn "Query gagal — kemungkinan tabel bronze.orders belum di-seed (scripts/seed-sample-data.sh)"
else
skip "Status query belum selesai (${state:-tidak diketahui})"
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────
main() {
print_section "Verifikasi Phase 2 — Kontrak Storage & Catalog (MinIO/S3 + Nessie)"
2026-10-11 15:09:43 +07:00
echo -e " Object storage : ${CYAN}${S3_ENDPOINT}${NC} (bucket ${BUCKET}, warehouse ${WAREHOUSE}, region ${S3_REGION})"
2026-10-09 02:35:14 +07:00
echo -e " Nessie : ${CYAN}${NESSIE_URL}${NC} (API host ${NESSIE_HOST_API})"
echo ""
check_object_storage
check_nessie
check_version_store
check_storage_contract
check_consumer_interfaces
if [[ ${DEEP} -eq 1 ]]; then
check_trino_read
else
echo -e "${INFO} Mode gate fase 2: uji baca via Trino DILEWATI (runtime fase 3) — pakai --deep untuk menjalankannya."
fi
echo ""
echo -e "${BOLD}Ringkasan:${NC} ${GREEN}${PASS_N} PASS${NC} · ${RED}${FAIL_N} FAIL${NC} · ${YELLOW}${WARN_N} WARN${NC} · ${YELLOW}${SKIP_N} SKIP${NC}"
if [[ ${FAIL_N} -gt 0 ]]; then
echo ""
echo -e "${BOLD}Yang gagal (${FAIL_N}):${NC}"
local _i=1 _m
for _m in "${FAIL_MSGS[@]}"; do
echo -e " ${RED}${_i}.${NC} ${_m}"
_i=$((_i + 1))
done
echo -e "${INFO} Perbaiki item di atas, lalu jalankan ulang: ./scripts/${0##*/}"
fi
if [[ ${FAIL_N} -eq 0 ]]; then
echo -e "${OK} ${GREEN}Kontrak storage Phase 2 terpenuhi — aman lanjut ke fase berikutnya.${NC}"
exit 0
fi
echo -e "${ERR} Ada ${FAIL_N} gate yang gagal — perbaiki sebelum melanjutkan."
exit 1
}
main "$@"