#!/usr/bin/env bash # ============================================================================= # test-bootstrap-host.sh — penjagaan untuk `bootstrap.sh --host` (Fase 2b/2c) # ----------------------------------------------------------------------------- # Latar: `generate_runtime_credentials()` menulis TIGA berkas ke `config/` BERSAMA. # Dalam mode `--host`, endpoint-nya milik host itu, sehingga berkas bersama tertimpa # alamat host lain — dan itu TIDAK terlihat di `git status` karena # `config/airflow/sling/env.yaml` memang gitignored. Dampaknya: deploy single-host # yang sedang berjalan menunjuk alamat yang tidak ada. # # Uji ini punya dua lapis: # A. STATIS — membaca bootstrap.sh dan memastikan ketiga penulisan memakai # `${cfg_base}` (bukan `${SCRIPT_DIR}/config` langsung). Tidak butuh # apa pun, selalu jalan. # B. DINAMIS — bila stack SiCABE sedang berjalan: hash ketiga berkas bersama, # jalankan `bootstrap.sh --host state` (hanya step 2 = pastikan bucket, # idempoten), lalu pastikan hash TIDAK berubah. # # Jalankan: ./scripts/test-bootstrap-host.sh # ============================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" GRN=$'\e[32m'; RED=$'\e[31m'; YLW=$'\e[33m'; BLD=$'\e[1m'; NC=$'\e[0m' PASS_N=0; FAIL_N=0 ok() { printf " ${GRN}✅${NC} %s\n" "$1"; PASS_N=$((PASS_N + 1)); } bad() { printf " ${RED}❌${NC} %s\n" "$1"; FAIL_N=$((FAIL_N + 1)); } inf() { printf " ${YLW}[..]${NC} %s\n" "$1"; } SHARED_FILES=( "config/airflow/sling/env.yaml" "config/airflow/simple_auth_manager_passwords.json" "config/trino/password.db" ) echo -e "${BLD}Uji bootstrap.sh --host${NC}" echo echo "── A. penjagaan statis (tanpa dependensi) ──" # Ekstrak badan fungsi generate_runtime_credentials. body="$(awk '/^generate_runtime_credentials\(\) \{/{f=1} f{print} f&&/^\}/{exit}' "${DEPLOY_DIR}/bootstrap.sh")" if [[ -z "${body}" ]]; then bad "fungsi generate_runtime_credentials() tidak ditemukan" else ok "fungsi generate_runtime_credentials() ditemukan" for target in 'airflow/sling' 'airflow/simple_auth_manager_passwords.json' 'trino/password.db'; do if grep -q 'local .*="\${cfg_base}/'"${target}"'"' <<<"${body}"; then ok "penulisan ${target} memakai \${cfg_base}" else bad "penulisan ${target} TIDAK memakai \${cfg_base} (rawan menimpa config bersama)" fi done if grep -q 'HOST_NAME' <<<"${body}" && grep -q 'generated/host-' <<<"${body}"; then ok "cfg_base dialihkan ke bundle host saat HOST_NAME diisi" else bad "cfg_base tidak dialihkan berdasarkan HOST_NAME" fi # Tidak boleh ada penulisan langsung ke ${SCRIPT_DIR}/config (selain default cfg_base). if grep -nE '(cat >|: >|>>) *"\$\{SCRIPT_DIR\}/config' <<<"${body}"; then bad "masih ada penulisan langsung ke \${SCRIPT_DIR}/config" else ok "tidak ada penulisan langsung ke \${SCRIPT_DIR}/config" fi fi # step_tiers harus memetakan seluruh step 1..9. Bentuk pattern bisa berkelompok # (mis. `1|3)`, `4|5)`, `7|8)`), jadi diambil hanya bagian sebelum ')' lalu dipecah # pada '|' — regex "baris diawali angka" akan salah untuk bentuk berkelompok. patterns="$(awk '/^step_tiers\(\) \{/{f=1} f{print} f&&/^\}/{exit}' "${DEPLOY_DIR}/bootstrap.sh" \ | grep -E '\)' | sed 's/).*//' | tr '|' '\n' | tr -d ' ')" missing="" for s in 1 2 3 4 5 6 7 8 9; do grep -qx "${s}" <<<"${patterns}" || missing+=" ${s}" done if [[ -z "${missing}" ]]; then ok "step_tiers() memetakan step 1..9" else bad "step_tiers() belum memetakan step:${missing}" fi echo echo "── B. penjagaan dinamis (butuh stack SiCABE berjalan) ──" if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^sicabe-'; then inf "tidak ada container sicabe- yang berjalan — lapis dinamis DILEWATI" echo printf " ${YLW}Ringkasan:${NC} %s PASS · %s FAIL (lapis dinamis dilewati)\n" "${PASS_N}" "${FAIL_N}" [[ ${FAIL_N} -eq 0 ]] && exit 0 || exit 1 fi GEN_DIR="${DEPLOY_DIR}/generated" created_gen=0 if [[ ! -f "${GEN_DIR}/host-state/.env" ]]; then inf "bundle host belum ada — generate dulu (sementara, akan dihapus)" if python3 "${DEPLOY_DIR}/lib/cluster/generate.py" render \ "${DEPLOY_DIR}/cluster.example.yml" "${DEPLOY_DIR}/docker-compose.yml" \ "${DEPLOY_DIR}/.env" "${GEN_DIR}" "${DEPLOY_DIR}/scripts/render-config.sh" \ >/dev/null 2>&1; then created_gen=1 else bad "gagal menyiapkan bundle host untuk uji dinamis" GEN_DIR="" fi fi if [[ -n "${GEN_DIR}" ]]; then declare -A before=() for f in "${SHARED_FILES[@]}"; do [[ -f "${DEPLOY_DIR}/${f}" ]] && before["${f}"]="$(sha256sum "${DEPLOY_DIR}/${f}" | cut -c1-16)" done inf "menjalankan: bootstrap.sh --host state (hanya step 2 — pastikan bucket)" "${DEPLOY_DIR}/bootstrap.sh" --host state >/tmp/test-bootstrap-host.log 2>&1 || true touched=0 for f in "${SHARED_FILES[@]}"; do [[ -z "${before[${f}]:-}" ]] && continue now="$(sha256sum "${DEPLOY_DIR}/${f}" | cut -c1-16)" if [[ "${before[${f}]}" != "${now}" ]]; then bad "${f} BERUBAH setelah --host state (bug config bersama kembali)" touched=1 fi done [[ ${touched} -eq 0 ]] && ok "ketiga berkas config bersama tidak berubah (sha256 identik)" # Kredensial host harus mendarat di bundle host. if [[ -f "${GEN_DIR}/host-state/config/airflow/sling/env.yaml" ]]; then ok "kredensial runtime ditulis ke bundle host (generated/host-state/config/...)" else bad "kredensial runtime TIDAK ditemukan di bundle host" fi fi [[ ${created_gen} -eq 1 ]] && rm -rf "${GEN_DIR}" && inf "artefak uji generated/ dihapus" rm -f /tmp/test-bootstrap-host.log # ── install.sh --host: profil `edge` ikut saat bundle memuat traefik (T-20) ───── # Traefik berada di profile `edge`. Bila run_host hanya memakai `--profile all`, # Traefik tidak pernah start sehingga SELURUH rute publik tidak ada — padahal label # sudah terpasang di setiap layanan dan sertifikat sudah bisa diterbitkan. run_host_body="$(sed -n '/^run_host()/,/^}/p' "${DEPLOY_DIR}/install.sh")" if grep -q 'traefik:' <<<"${run_host_body}" && grep -q 'profile edge' <<<"${run_host_body}"; then ok "run_host menyertakan profile edge bila bundle memuat traefik" else bad "run_host tidak mendeteksi profile edge (Traefik tidak akan pernah start)" fi # ── install.sh: nama LOGIS harus diuraikan di EXTERNAL_SERVICES/LOCAL_SERVICES ── # Tanpa ini, `starrocks` (dan dulu hanya `trino` yang istimewa) diam-diam tidak # berefek: filter mencocokkan nama SERVICE (`starrocks-fe`, `starrocks-cn`). if grep -q 'expand_service_names()' "${DEPLOY_DIR}/install.sh" \ && grep -q 'starrocks-fe starrocks-cn' "${DEPLOY_DIR}/install.sh" \ && grep -q 'expand_service_names ${LOCAL_SERVICES' "${DEPLOY_DIR}/install.sh" \ && grep -q 'expand_service_names ${EXTERNAL_SERVICES' "${DEPLOY_DIR}/install.sh"; then ok "nama logis diuraikan di EXTERNAL_SERVICES & LOCAL_SERVICES (starrocks/trino/…)" else bad "pemetaan nama logis hilang — setelan seperti LOCAL_SERVICES=starrocks akan diabaikan" fi echo if [[ ${FAIL_N} -eq 0 ]]; then echo -e " ${GRN}${BLD}SEMUA LULUS${NC} (${PASS_N} cek)" exit 0 fi echo -e " ${RED}${BLD}GAGAL${NC}: ${FAIL_N} masalah · ${PASS_N} lulus" exit 1