235 lines
11 KiB
Bash
235 lines
11 KiB
Bash
#!/usr/bin/env bash
|
||||
|
|
# =============================================================================
|
|||
|
|
# eBdesk SiCABE — Common Library (shared by install.sh, bootstrap.sh, test-verification.sh)
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
# ─── Color & Styling ─────────────────────────────────────────────────────────
|
|||
|
|
export RED='\033[0;31m'
|
|||
|
|
export GREEN='\033[0;32m'
|
|||
|
|
export YELLOW='\033[1;33m'
|
|||
|
|
export BLUE='\033[0;34m'
|
|||
|
|
export CYAN='\033[0;36m'
|
|||
|
|
export BOLD='\033[1m'
|
|||
|
|
export NC='\033[0m'
|
|||
|
|
|
|||
|
|
export INFO="${CYAN}[INFO]${NC}"
|
|||
|
|
export OK="${GREEN}[OK]${NC}"
|
|||
|
|
export WARN="${YELLOW}[WARN]${NC}"
|
|||
|
|
export ERR="${RED}[ERROR]${NC}"
|
|||
|
|
export STEP="${BOLD}[STEP]${NC}"
|
|||
|
|
export PASS="${GREEN}[PASS]${NC}"
|
|||
|
|
export FAIL="${RED}[FAIL]${NC}"
|
|||
|
|
|
|||
|
|
# ─── Named Constants (Timeouts) ─────────────────────────────────────────────-
|
|||
|
|
# Default max wait times in seconds (override via env var)
|
|||
|
|
export WAIT_SHORT="${WAIT_SHORT:-30}" # ~30s — quick services
|
|||
|
|
export WAIT_MEDIUM="${WAIT_MEDIUM:-60}" # ~60s — most services
|
|||
|
|
export WAIT_LONG="${WAIT_LONG:-120}" # ~120s — slow services (Keycloak, StarRocks)
|
|||
|
|
export WAIT_EXTRA="${WAIT_EXTRA:-180}" # ~180s — OpenMetadata
|
|||
|
|
export SLEEP_STANDARD="${SLEEP_STANDARD:-2}" # polling interval
|
|||
|
|
export SLEEP_SLOW="${SLEEP_SLOW:-5}" # slow polling (StarRocks)
|
|||
|
|
|
|||
|
|
# ─── Helper: Wait for HTTP endpoint ─────────────────────────────────────────
|
|||
|
|
# Usage: wait_for_service "name" "http://url/health" [max_attempts]
|
|||
|
|
# Selalu return 0 (non-fatal): readiness wait saja. Kegagalan nyata ditangkap
|
|||
|
|
# test-verification.sh. Return non-zero akan mematikan script di bawah `set -e`.
|
|||
|
|
wait_for_service() {
|
|||
|
|
local name="$1"
|
|||
|
|
local url="$2"
|
|||
|
|
local max_attempts="${3:-${WAIT_MEDIUM}}"
|
|||
|
|
local attempt=0
|
|||
|
|
local sleep_time="${SLEEP_STANDARD}"
|
|||
|
|
|
|||
|
|
echo -e "${INFO} Menunggu ${name} siap (maks ${max_attempts}s)..."
|
|||
|
|
while [[ ${attempt} -lt ${max_attempts} ]]; do
|
|||
|
|
if curl -skf "${url}" >/dev/null 2>&1; then
|
|||
|
|
echo -e "${OK} ${name} siap!"
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
attempt=$((attempt + sleep_time))
|
|||
|
|
sleep "${sleep_time}"
|
|||
|
|
done
|
|||
|
|
echo -e "${WARN} ${name} belum siap dalam ${max_attempts}s. Lanjutkan... (cek nanti: curl ${url})"
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: Wait for container readiness via Docker inspect ────────────────
|
|||
|
|
# Usage: wait_for_container "container_name" [max_attempts]
|
|||
|
|
wait_for_container() {
|
|||
|
|
local container="$1"
|
|||
|
|
local max_attempts="${2:-${WAIT_MEDIUM}}"
|
|||
|
|
local attempt=0
|
|||
|
|
local sleep_time="${SLEEP_STANDARD}"
|
|||
|
|
|
|||
|
|
echo -e "${INFO} Menunggu container ${container} running..."
|
|||
|
|
while [[ ${attempt} -lt ${max_attempts} ]]; do
|
|||
|
|
if docker ps --format "{{.Names}}" | grep -q "${container}"; then
|
|||
|
|
echo -e "${OK} Container ${container} running."
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
attempt=$((attempt + sleep_time))
|
|||
|
|
sleep "${sleep_time}"
|
|||
|
|
done
|
|||
|
|
echo -e "${WARN} Container ${container} belum running dalam ${max_attempts}s."
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: JSON field extractor ───────────────────────────────────────────
|
|||
|
|
# Usage: extract_json "field_name" < <json_string>
|
|||
|
|
# Requires python3
|
|||
|
|
extract_json() {
|
|||
|
|
local field="$1"
|
|||
|
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('${field}',''))" 2>/dev/null || echo ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: Check if a command exists ──────────────────────────────────────
|
|||
|
|
check_command() {
|
|||
|
|
local cmd="$1"
|
|||
|
|
local name="${2:-$1}"
|
|||
|
|
if command -v "${cmd}" &>/dev/null; then
|
|||
|
|
echo -e "${OK} ${name}: tersedia ($(command -v "${cmd}"))"
|
|||
|
|
return 0
|
|||
|
|
else
|
|||
|
|
echo -e "${WARN} ${name} tidak ditemukan."
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: Print section header ───────────────────────────────────────────
|
|||
|
|
print_section() {
|
|||
|
|
local title="$1"
|
|||
|
|
echo ""
|
|||
|
|
echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}"
|
|||
|
|
echo -e "${BOLD} ${title}${NC}"
|
|||
|
|
echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}"
|
|||
|
|
echo ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: Muat berkas env (dukung mode multi-host) ───────────────────────
|
|||
|
|
# Prioritas sumber env: --env-file <berkas> > $SICABE_ENV_FILE > <base>/.env
|
|||
|
|
#
|
|||
|
|
# KENAPA: di multi-host, endpoint (POSTGRES_HOST, TRINO_URL, KEYCLOAK_URL, …) BERBEDA
|
|||
|
|
# per host dan ada di `generated/host-<x>/.env` — BUKAN di `deploy/.env`. Tanpa ini
|
|||
|
|
# skrip memakai nilai single-host (localhost) dan memeriksa/menyentuh endpoint salah.
|
|||
|
|
#
|
|||
|
|
# Dipakai: `sicabe_load_env "${SCRIPT_DIR}" "$@"` — GANTI blok `source .env` inline.
|
|||
|
|
# - `--env-file <berkas>` dipilih (relatif dihitung dari <base>).
|
|||
|
|
# - `$SICABE_ENV_FILE` dihormati agar sub-script yang dipanggil `bootstrap.sh --host`
|
|||
|
|
# otomatis mewarisi env host tanpa menimpa dengan deploy/.env.
|
|||
|
|
# - `source` berjalan di shell PEMANGGIL (fungsi tidak membuat subshell), jadi
|
|||
|
|
# variabel tetap terlihat setelah fungsi kembali.
|
|||
|
|
sicabe_load_env() {
|
|||
|
|
local _sicabe_base="$1"; shift
|
|||
|
|
local _sicabe_override="${SICABE_ENV_FILE:-}" _sicabe_prev="" _sicabe_a
|
|||
|
|
for _sicabe_a in "$@"; do
|
|||
|
|
[[ "${_sicabe_prev}" == "--env-file" ]] && _sicabe_override="${_sicabe_a}"
|
|||
|
|
_sicabe_prev="${_sicabe_a}"
|
|||
|
|
done
|
|||
|
|
local _sicabe_envf="${_sicabe_override:-${_sicabe_base}/.env}"
|
|||
|
|
[[ "${_sicabe_envf}" != /* ]] && _sicabe_envf="${_sicabe_base}/${_sicabe_envf}"
|
|||
|
|
if [[ ! -f "${_sicabe_envf}" ]]; then
|
|||
|
|
if [[ -n "${_sicabe_override}" ]]; then
|
|||
|
|
echo -e "${ERR} env-file tidak ditemukan: ${_sicabe_envf}" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
set -a
|
|||
|
|
# shellcheck disable=SC1090
|
|||
|
|
source "${_sicabe_envf}"
|
|||
|
|
set +a
|
|||
|
|
# Direktori bundle (config + docker-compose.yml + .env) — dipakai helper host-aware
|
|||
|
|
# untuk tahu container mana milik host ini (lihat sicabe_host_expects_container).
|
|||
|
|
SICABE_BUNDLE_DIR="$(dirname "${_sicabe_envf}")"
|
|||
|
|
[[ -n "${_sicabe_override}" ]] && echo -e "${INFO} env-file: ${_sicabe_envf} (mode host)" >&2
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: URL endpoint efektif (sadar EXTERNAL & multi-host) ─────────────
|
|||
|
|
# Urutan: (1) <url_var> eksplisit NON-localhost (komponen EXTERNAL) → pakai apa adanya;
|
|||
|
|
# (2) multi-host (bundle memuat SICABE_HOST_NAME) → scheme://<host_var>:<port>;
|
|||
|
|
# (3) selain itu → scheme://localhost:<port> (single-host).
|
|||
|
|
# Dipakai `scripts/verify-phase*` agar endpoint menunjuk ALAMAT HOST di multi-host,
|
|||
|
|
# tanpa merusak dukungan komponen existing (single-host tetap localhost; external
|
|||
|
|
# tetap memakai URL yang diberikan operator).
|
|||
|
|
sicabe_local_url() { # <scheme> <host_var> <port> [url_var]
|
|||
|
|
local _slu_s="$1" _slu_hv="$2" _slu_p="$3" _slu_uv="${4:-}" _slu_u="" _slu_h=""
|
|||
|
|
[[ -n "${_slu_uv}" ]] && _slu_u="${!_slu_uv:-}"
|
|||
|
|
if [[ -n "${_slu_u}" && "${_slu_u}" != *localhost* && "${_slu_u}" != *127.0.0.1* ]]; then
|
|||
|
|
echo "${_slu_u}"; return 0
|
|||
|
|
fi
|
|||
|
|
_slu_h="${!_slu_hv:-}"
|
|||
|
|
# Remote ⟺ nilai berupa ALAMAT (mengandung titik: IP pemilik, mis. 100.112.76.124).
|
|||
|
|
# Nama layanan tanpa titik (mis. `airflow-webserver`, `keycloak`, `openmetadata-server`)
|
|||
|
|
# = komponen CO-LOCATED → dari host dijangkau via localhost. Uji LAMA ("≠ localhost")
|
|||
|
|
# keliru menganggap nama layanan sebagai remote → `http://airflow-webserver:12082`
|
|||
|
|
# (nama container + port HOST) yang tak bisa diakses dari host.
|
|||
|
|
if [[ -n "${SICABE_HOST_NAME:-}" && "${_slu_h}" == *.* && "${_slu_h}" != "127.0.0.1" ]]; then
|
|||
|
|
echo "${_slu_s}://${_slu_h}:${_slu_p}"; return 0
|
|||
|
|
fi
|
|||
|
|
echo "${_slu_s}://localhost:${_slu_p}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: HOST endpoint efektif (sadar EXTERNAL & multi-host) ────────────
|
|||
|
|
# Urutan: (1) <fallback_var> bila NON-localhost (override operator / komponen EXTERNAL);
|
|||
|
|
# (2) multi-host (SICABE_HOST_NAME) → <host_var>;
|
|||
|
|
# (3) selain itu → localhost.
|
|||
|
|
# Dipakai untuk variabel yang di skrip SEKALIGUS menjadi penanda external
|
|||
|
|
# (mis. STARROCKS_HOST/AIRFLOW_HOST): penanda tetap memakai nilai operator, sedangkan
|
|||
|
|
# koneksi memakai host efektif ini.
|
|||
|
|
sicabe_local_host() { # <host_var> [fallback_var]
|
|||
|
|
local _slh_hv="$1" _slh_fb="${2:-}" _slh_f=""
|
|||
|
|
if [[ -n "${_slh_fb}" ]]; then
|
|||
|
|
_slh_f="${!_slh_fb:-}"
|
|||
|
|
# Remote = ALAMAT (mengandung titik). Nama layanan tanpa titik = co-located → localhost.
|
|||
|
|
if [[ "${_slh_f}" == *.* && "${_slh_f}" != "127.0.0.1" ]]; then
|
|||
|
|
echo "${_slh_f}"; return 0
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
local _slh_h="${!_slh_hv:-}"
|
|||
|
|
if [[ -n "${SICABE_HOST_NAME:-}" && "${_slh_h}" == *.* && "${_slh_h}" != "127.0.0.1" ]]; then
|
|||
|
|
echo "${_slh_h}"; return 0
|
|||
|
|
fi
|
|||
|
|
echo "localhost"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: apakah container milik HOST INI? ───────────────────────────────
|
|||
|
|
# Single-host (SICABE_HOST_NAME kosong) → selalu true (semua lokal).
|
|||
|
|
# Multi-host (dijalankan dgn `--env-file generated/host-<x>/.env`) → true hanya bila
|
|||
|
|
# container ada di bundle host ini. Dipakai gate agar container host LAIN di-SKIP
|
|||
|
|
# (bukan FAIL) — sama seperti mode host di test-verification.sh.
|
|||
|
|
# Daftar container di-cache (1× `docker compose config` per proses).
|
|||
|
|
sicabe_host_expects_container() { # <nama-container>
|
|||
|
|
[[ -z "${SICABE_HOST_NAME:-}" ]] && return 0
|
|||
|
|
local _shc_dir="${SICABE_BUNDLE_DIR:-.}"
|
|||
|
|
if [[ -z "${SICABE_BUNDLE_CONTAINERS+x}" ]]; then
|
|||
|
|
SICABE_BUNDLE_CONTAINERS="$(docker compose -f "${_shc_dir}/docker-compose.yml" \
|
|||
|
|
--env-file "${_shc_dir}/.env" --profile all config 2>/dev/null \
|
|||
|
|
| python3 -c "import sys,yaml;d=yaml.safe_load(sys.stdin) or {};print('\n'.join((s.get('container_name') or '') for s in (d.get('services') or {}).values()))" \
|
|||
|
|
2>/dev/null || true)"
|
|||
|
|
fi
|
|||
|
|
printf '%s\n' "${SICABE_BUNDLE_CONTAINERS}" | grep -qx "$1"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: wajibkan container LOKAL (kalau bukan → instruksi + kode 3) ─────
|
|||
|
|
# Dipakai skrip seed/reapply yang `docker exec` container tertentu. Di multi-host,
|
|||
|
|
# bila container itu milik host LAIN, skrip harus dijalankan DI host pemiliknya.
|
|||
|
|
sicabe_require_local_container() { # <container> <nama-komponen> <host-contoh>
|
|||
|
|
sicabe_host_expects_container "$1" && return 0
|
|||
|
|
echo -e "${WARN} '${1}' (${2}) berjalan di host LAIN — skrip ini harus dijalankan DI host itu." >&2
|
|||
|
|
echo -e "${WARN} contoh: ./scripts/${0##*/} --env-file generated/host-${3}/.env (di host ${3})" >&2
|
|||
|
|
return 3
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ─── Helper: pastikan sumber bind-mount berupa FILE (bukan direktori) ─────────
|
|||
|
|
# Docker membuat DIRECTORY bila sumber bind-mount sebuah file TIDAK ADA saat `up`
|
|||
|
|
# (bind mount ke path non-existent di host). Akibatnya service gagal, mis.
|
|||
|
|
# Airflow SimpleAuthManager: IsADirectoryError '/opt/airflow/simple_auth_manager_pa...'.
|
|||
|
|
# Bila path sudah berupa direktori (sisa `up` sebelumnya) → buang; lalu pastikan ada file.
|
|||
|
|
sicabe_ensure_file_mount() { # <path> [mode]
|
|||
|
|
local path="$1" mode="${2:-644}"
|
|||
|
|
if [[ -d "${path}" ]]; then rm -rf "${path}"; fi
|
|||
|
|
[[ -e "${path}" ]] || : > "${path}"
|
|||
|
|
chmod "${mode}" "${path}" 2>/dev/null || true
|
|||
|
|
}
|