429 lines
13 KiB
Bash
Executable File
429 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
CONFIG_DIR="${SCRIPT_DIR}/Config"
|
|
GLOBAL_ENV_FILE_DEFAULT="${CONFIG_DIR}/global.env"
|
|
TARGETS_DIR_DEFAULT="${CONFIG_DIR}/Targets"
|
|
GIT_TOPLEVEL="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
LOCAL_REPO_SUBDIR_DEFAULT=""
|
|
|
|
if [[ -n "$GIT_TOPLEVEL" && "$SCRIPT_DIR" == "$GIT_TOPLEVEL"/* ]]; then
|
|
LOCAL_REPO_SUBDIR_DEFAULT="${SCRIPT_DIR#"$GIT_TOPLEVEL"/}"
|
|
fi
|
|
|
|
GLOBAL_ENV_FILE="${GLOBAL_ENV_FILE:-$GLOBAL_ENV_FILE_DEFAULT}"
|
|
TARGETS_DIR="${TARGETS_DIR:-$TARGETS_DIR_DEFAULT}"
|
|
|
|
CLI_TARGET=""
|
|
CLI_DB=""
|
|
CLI_OVERWRITE=""
|
|
CLI_RESTORE_ROLES=""
|
|
CLI_REQUEST_ID=""
|
|
NON_INTERACTIVE="${NON_INTERACTIVE:-no}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--global-env-file)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --global-env-file" >&2; exit 1; }
|
|
GLOBAL_ENV_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--targets-dir)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --targets-dir" >&2; exit 1; }
|
|
TARGETS_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--target)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --target" >&2; exit 1; }
|
|
CLI_TARGET="$2"
|
|
shift 2
|
|
;;
|
|
--db)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --db" >&2; exit 1; }
|
|
CLI_DB="$2"
|
|
shift 2
|
|
;;
|
|
--overwrite)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --overwrite" >&2; exit 1; }
|
|
CLI_OVERWRITE="$2"
|
|
shift 2
|
|
;;
|
|
--restore-roles)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --restore-roles" >&2; exit 1; }
|
|
CLI_RESTORE_ROLES="$2"
|
|
shift 2
|
|
;;
|
|
--request-id)
|
|
[[ $# -ge 2 ]] || { echo "Argument manquant pour --request-id" >&2; exit 1; }
|
|
CLI_REQUEST_ID="$2"
|
|
shift 2
|
|
;;
|
|
--non-interactive)
|
|
NON_INTERACTIVE="yes"
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Argument inconnu : $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
|
}
|
|
|
|
fail() {
|
|
log "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
|
|
}
|
|
|
|
to_bool_yes_no() {
|
|
local v="${1:-}"
|
|
v="${v,,}"
|
|
case "$v" in
|
|
yes|y|oui|o|true|1) echo "yes" ;;
|
|
no|n|non|false|0|"") echo "no" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
is_tty() {
|
|
[[ -t 0 && -t 1 ]]
|
|
}
|
|
|
|
shell_quote() {
|
|
printf "%q" "$1"
|
|
}
|
|
|
|
cleanup() {
|
|
rm -f "${BOOTSTRAP_JSON:-}" "${REMOTE_RESULT_JSON:-}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
[[ -f "$GLOBAL_ENV_FILE" ]] || fail "fichier global introuvable : $GLOBAL_ENV_FILE"
|
|
[[ -d "$TARGETS_DIR" ]] || fail "dossier targets introuvable : $TARGETS_DIR"
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$GLOBAL_ENV_FILE"
|
|
set +a
|
|
|
|
require_cmd ssh
|
|
require_cmd git
|
|
require_cmd python3
|
|
|
|
TARGET="${CLI_TARGET:-${TARGET:-}}"
|
|
REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}"
|
|
ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}"
|
|
RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}"
|
|
REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}"
|
|
|
|
ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide"
|
|
RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide"
|
|
|
|
if [[ -z "$TARGET" ]]; then
|
|
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
|
|
fail "TARGET manquante en mode non interactif"
|
|
fi
|
|
|
|
mapfile -t TARGET_LIST < <(find "$TARGETS_DIR" -maxdepth 1 -type f -name '*.env' -printf '%f\n' | sed 's/\.env$//' | LC_ALL=C sort)
|
|
|
|
[[ "${#TARGET_LIST[@]}" -gt 0 ]] || fail "aucune cible définie dans ${TARGETS_DIR}"
|
|
|
|
if is_tty; then
|
|
echo "Cibles disponibles :"
|
|
for i in "${!TARGET_LIST[@]}"; do
|
|
echo " $((i + 1))) ${TARGET_LIST[$i]}"
|
|
done
|
|
echo
|
|
read -r -p "Sélectionnez le numéro de la cible : " TARGET_INDEX
|
|
[[ "$TARGET_INDEX" =~ ^[0-9]+$ ]] || fail "numéro de cible invalide"
|
|
(( TARGET_INDEX >= 1 && TARGET_INDEX <= ${#TARGET_LIST[@]} )) || fail "numéro hors plage"
|
|
TARGET="${TARGET_LIST[$((TARGET_INDEX - 1))]}"
|
|
else
|
|
fail "TARGET manquante et aucune interaction terminal disponible"
|
|
fi
|
|
fi
|
|
|
|
TARGET_ENV_SOURCE="${TARGETS_DIR}/${TARGET}.env"
|
|
[[ -f "$TARGET_ENV_SOURCE" ]] || fail "fichier cible introuvable : $TARGET_ENV_SOURCE"
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$TARGET_ENV_SOURCE"
|
|
set +a
|
|
|
|
TARGET_HOST="${TARGET_HOST:-}"
|
|
TARGET_PORT="${TARGET_PORT:-22}"
|
|
TARGET_USER="${TARGET_BOOTSTRAP_USER:-}"
|
|
TARGET_SSH_KEY="${TARGET_BOOTSTRAP_SSH_KEY:-}"
|
|
TARGET_REPO_URL="${TARGET_REPO_URL:-${GLOBAL_REPO_URL:-}}"
|
|
TARGET_REPO_BRANCH="${TARGET_REPO_BRANCH:-${GLOBAL_REPO_BRANCH:-main}}"
|
|
TARGET_REPO_DIR="${TARGET_REPO_DIR:-}"
|
|
TARGET_REPO_SUBDIR="${TARGET_REPO_SUBDIR:-$LOCAL_REPO_SUBDIR_DEFAULT}"
|
|
TARGET_ENV_FILE="${TARGET_ENV_FILE:-}"
|
|
TARGET_ENABLE_BOOTSTRAP="${TARGET_ENABLE_BOOTSTRAP:-${GLOBAL_ENABLE_BOOTSTRAP:-yes}}"
|
|
|
|
[[ -n "$TARGET_HOST" ]] || fail "TARGET_HOST manquante"
|
|
[[ "$TARGET_PORT" =~ ^[0-9]+$ ]] || fail "TARGET_PORT invalide"
|
|
[[ -n "$TARGET_USER" ]] || fail "TARGET_BOOTSTRAP_USER manquante"
|
|
[[ -n "$TARGET_SSH_KEY" ]] || fail "TARGET_BOOTSTRAP_SSH_KEY manquante"
|
|
[[ -f "$TARGET_SSH_KEY" ]] || fail "clé SSH cible introuvable : $TARGET_SSH_KEY"
|
|
[[ -r "$TARGET_SSH_KEY" ]] || fail "clé SSH cible non lisible : $TARGET_SSH_KEY"
|
|
|
|
[[ -n "$TARGET_REPO_URL" ]] || fail "GLOBAL_REPO_URL/TARGET_REPO_URL manquant"
|
|
[[ -n "$TARGET_REPO_BRANCH" ]] || fail "GLOBAL_REPO_BRANCH/TARGET_REPO_BRANCH manquant"
|
|
[[ -n "$TARGET_REPO_DIR" ]] || fail "TARGET_REPO_DIR manquante"
|
|
[[ -n "$TARGET_ENV_FILE" ]] || fail "TARGET_ENV_FILE manquante"
|
|
|
|
TARGET_REPO_SUBDIR="${TARGET_REPO_SUBDIR#/}"
|
|
TARGET_REPO_SUBDIR="${TARGET_REPO_SUBDIR%/}"
|
|
|
|
TARGET_CLONE_DIR="$TARGET_REPO_DIR"
|
|
TARGET_SCRIPT_DIR="$TARGET_REPO_DIR"
|
|
if [[ -n "$TARGET_REPO_SUBDIR" ]]; then
|
|
if [[ "$TARGET_REPO_DIR" == */"$TARGET_REPO_SUBDIR" ]]; then
|
|
TARGET_CLONE_DIR="$(dirname "$TARGET_REPO_DIR")"
|
|
else
|
|
TARGET_SCRIPT_DIR="${TARGET_REPO_DIR}/${TARGET_REPO_SUBDIR}"
|
|
fi
|
|
fi
|
|
|
|
TARGET_ENABLE_BOOTSTRAP="$(to_bool_yes_no "$TARGET_ENABLE_BOOTSTRAP")" || fail "TARGET_ENABLE_BOOTSTRAP invalide"
|
|
|
|
BOOTSTRAP_SCRIPT_LOCAL="${SCRIPT_DIR}/bootstrap-target-host.sh"
|
|
[[ -f "$BOOTSTRAP_SCRIPT_LOCAL" ]] || fail "script bootstrap introuvable : $BOOTSTRAP_SCRIPT_LOCAL"
|
|
[[ -x "$BOOTSTRAP_SCRIPT_LOCAL" ]] || chmod 700 "$BOOTSTRAP_SCRIPT_LOCAL" || fail "chmod impossible sur $BOOTSTRAP_SCRIPT_LOCAL"
|
|
|
|
if [[ -z "$REQUESTED_DB" ]]; then
|
|
DBS_FOR_TARGET="${TARGET_DBS:-}"
|
|
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
|
|
fail "REQUESTED_DB manquante en mode non interactif"
|
|
fi
|
|
|
|
read -r -a DBS_ARRAY <<< "$DBS_FOR_TARGET"
|
|
[[ "${#DBS_ARRAY[@]}" -gt 0 ]] || fail "TARGET_DBS vide"
|
|
|
|
if is_tty; then
|
|
echo "Bases disponibles :"
|
|
for i in "${!DBS_ARRAY[@]}"; do
|
|
echo " $((i + 1))) ${DBS_ARRAY[$i]}"
|
|
done
|
|
echo
|
|
read -r -p "Nom exact de la base à restaurer : " REQUESTED_DB
|
|
else
|
|
fail "REQUESTED_DB manquante et aucune interaction terminal disponible"
|
|
fi
|
|
fi
|
|
|
|
[[ "$REQUESTED_DB" =~ ^[a-zA-Z0-9_]+$ ]] || fail "nom de base invalide"
|
|
|
|
if [[ "$TARGET_ENABLE_BOOTSTRAP" == "yes" ]]; then
|
|
log "Bootstrap initial activé pour la cible ${TARGET}"
|
|
BOOTSTRAP_JSON="/tmp/bootstrap_target_${REQUEST_ID}.json"
|
|
|
|
"$BOOTSTRAP_SCRIPT_LOCAL" \
|
|
--global-env-file "$GLOBAL_ENV_FILE" \
|
|
--targets-dir "$TARGETS_DIR" \
|
|
--target "$TARGET" \
|
|
--json-only >"$BOOTSTRAP_JSON" || {
|
|
cat "$BOOTSTRAP_JSON" 2>/dev/null || true
|
|
fail "échec du bootstrap initial de la cible ${TARGET}"
|
|
}
|
|
|
|
BOOTSTRAP_STATUS="$(
|
|
python3 - <<'PY' "$BOOTSTRAP_JSON"
|
|
import json, sys
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
print(data.get("status", "error"))
|
|
PY
|
|
)"
|
|
|
|
if [[ "$BOOTSTRAP_STATUS" != "success" ]]; then
|
|
cat "$BOOTSTRAP_JSON"
|
|
fail "bootstrap initial échoué pour la cible ${TARGET}"
|
|
fi
|
|
|
|
log "Bootstrap initial terminé pour ${TARGET}"
|
|
else
|
|
log "Bootstrap initial désactivé pour ${TARGET}"
|
|
fi
|
|
|
|
SSH_OPTS=(
|
|
-i "$TARGET_SSH_KEY"
|
|
-p "$TARGET_PORT"
|
|
-o IdentitiesOnly=yes
|
|
-o BatchMode=yes
|
|
-o StrictHostKeyChecking=accept-new
|
|
-o ConnectTimeout=8
|
|
)
|
|
|
|
ssh "${SSH_OPTS[@]}" "${TARGET_USER}@${TARGET_HOST}" "exit 0" >/dev/null 2>&1 \
|
|
|| fail "connexion SSH impossible vers la cible ${TARGET_USER}@${TARGET_HOST}"
|
|
|
|
TARGET_CORE_SCRIPT="${TARGET_SCRIPT_DIR}/rebuild-bdd-core.sh"
|
|
REMOTE_RESULT_JSON="/tmp/run_rebuild_bdd_${REQUEST_ID}.json"
|
|
|
|
REMOTE_BOOTSTRAP_CMD="
|
|
set -euo pipefail
|
|
|
|
CLONE_DIR=$(shell_quote "$TARGET_CLONE_DIR")
|
|
REPO_DIR=$(shell_quote "$TARGET_SCRIPT_DIR")
|
|
REPO_URL=$(shell_quote "$TARGET_REPO_URL")
|
|
REPO_BRANCH=$(shell_quote "$TARGET_REPO_BRANCH")
|
|
CORE_SCRIPT=$(shell_quote "$TARGET_CORE_SCRIPT")
|
|
PRECHECK_SCRIPT=$(shell_quote "${TARGET_SCRIPT_DIR}/Checkup/check-target-readiness.sh")
|
|
TARGET_ENV_FILE=$(shell_quote "$TARGET_ENV_FILE")
|
|
REQUESTED_DB=$(shell_quote "$REQUESTED_DB")
|
|
ALLOW_OVERWRITE=$(shell_quote "$ALLOW_OVERWRITE")
|
|
RESTORE_ROLES=$(shell_quote "$RESTORE_ROLES")
|
|
REQUEST_ID=$(shell_quote "$REQUEST_ID")
|
|
|
|
command -v git >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"git absent sur la cible\"}'; exit 1; }
|
|
command -v bash >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"bash absent sur la cible\"}'; exit 1; }
|
|
command -v python3 >/dev/null 2>&1 || { echo '{\"status\":\"error\",\"message\":\"python3 absent sur la cible\"}'; exit 1; }
|
|
|
|
mkdir -p \"\$(dirname \"\$CLONE_DIR\")\"
|
|
mkdir -p \"\$(dirname \"\$REPO_DIR\")\"
|
|
|
|
if [[ ! -d \"\$CLONE_DIR/.git\" ]]; then
|
|
rm -rf \"\$CLONE_DIR\"
|
|
git clone --branch \"\$REPO_BRANCH\" --single-branch \"\$REPO_URL\" \"\$CLONE_DIR\" >/dev/null 2>&1
|
|
else
|
|
git -C \"\$CLONE_DIR\" fetch --prune origin >/dev/null 2>&1
|
|
git -C \"\$CLONE_DIR\" checkout -f \"\$REPO_BRANCH\" >/dev/null 2>&1
|
|
git -C \"\$CLONE_DIR\" reset --hard \"origin/\$REPO_BRANCH\" >/dev/null 2>&1
|
|
fi
|
|
|
|
[[ -f \"\$CORE_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script core introuvable sur la cible\"}'; exit 1; }
|
|
[[ -f \"\$PRECHECK_SCRIPT\" ]] || { echo '{\"status\":\"error\",\"message\":\"script précheck introuvable sur la cible\"}'; exit 1; }
|
|
|
|
chmod 700 \"\$CORE_SCRIPT\"
|
|
chmod 700 \"\$PRECHECK_SCRIPT\"
|
|
|
|
PRECHECK_JSON=\"/tmp/check_target_\${REQUEST_ID}.json\"
|
|
PRECHECK_STDERR=\"/tmp/check_target_\${REQUEST_ID}.stderr\"
|
|
CORE_JSON=\"/tmp/rebuild_target_\${REQUEST_ID}.json\"
|
|
CORE_STDERR=\"/tmp/rebuild_target_\${REQUEST_ID}.stderr\"
|
|
|
|
\"\$PRECHECK_SCRIPT\" \
|
|
--env-file \"\$TARGET_ENV_FILE\" \
|
|
--request-id \"\$REQUEST_ID\" \
|
|
--non-interactive \
|
|
--json-only >\"\$PRECHECK_JSON\" 2>\"\$PRECHECK_STDERR\" || {
|
|
cat \"\$PRECHECK_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$PRECHECK_JSON\" 2>/dev/null || true
|
|
rm -f \"\$PRECHECK_JSON\" \"\$PRECHECK_STDERR\" \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
}
|
|
|
|
PRECHECK_STATUS=\"\$(python3 - <<'PY' \"\$PRECHECK_JSON\"
|
|
import json, sys
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
print(data.get('status', 'error'))
|
|
PY
|
|
)\" || {
|
|
cat \"\$PRECHECK_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$PRECHECK_JSON\" 2>/dev/null || true
|
|
rm -f \"\$PRECHECK_JSON\" \"\$PRECHECK_STDERR\" \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
}
|
|
|
|
if [[ \"\$PRECHECK_STATUS\" != \"success\" ]]; then
|
|
cat \"\$PRECHECK_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$PRECHECK_JSON\"
|
|
rm -f \"\$PRECHECK_JSON\" \"\$PRECHECK_STDERR\" \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
fi
|
|
|
|
rm -f \"\$PRECHECK_JSON\" \"\$PRECHECK_STDERR\"
|
|
|
|
\"\$CORE_SCRIPT\" \
|
|
--env-file \"\$TARGET_ENV_FILE\" \
|
|
--db \"\$REQUESTED_DB\" \
|
|
--overwrite \"\$ALLOW_OVERWRITE\" \
|
|
--restore-roles \"\$RESTORE_ROLES\" \
|
|
--request-id \"\$REQUEST_ID\" \
|
|
--non-interactive \
|
|
--json-only >\"\$CORE_JSON\" 2>\"\$CORE_STDERR\" || {
|
|
cat \"\$CORE_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$CORE_JSON\" 2>/dev/null || true
|
|
rm -f \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
}
|
|
|
|
CORE_STATUS=\"\$(python3 - <<'PY' \"\$CORE_JSON\"
|
|
import json, sys
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
print(data.get('status', 'error'))
|
|
PY
|
|
)\" || {
|
|
cat \"\$CORE_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$CORE_JSON\" 2>/dev/null || true
|
|
rm -f \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
}
|
|
|
|
if [[ \"\$CORE_STATUS\" != \"success\" ]]; then
|
|
cat \"\$CORE_STDERR\" >&2 2>/dev/null || true
|
|
cat \"\$CORE_JSON\"
|
|
rm -f \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
exit 1
|
|
fi
|
|
|
|
cat \"\$CORE_JSON\"
|
|
rm -f \"\$CORE_JSON\" \"\$CORE_STDERR\"
|
|
"
|
|
|
|
ssh "${SSH_OPTS[@]}" "${TARGET_USER}@${TARGET_HOST}" "$REMOTE_BOOTSTRAP_CMD" >"$REMOTE_RESULT_JSON" 2>&1 \
|
|
|| {
|
|
cat "$REMOTE_RESULT_JSON" 2>/dev/null || true
|
|
fail "échec d'exécution distante sur la cible ${TARGET}"
|
|
}
|
|
|
|
REMOTE_STATUS="$(
|
|
python3 - <<'PY' "$REMOTE_RESULT_JSON"
|
|
import json, sys
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
print(data.get("status", "error"))
|
|
PY
|
|
)" || {
|
|
cat "$REMOTE_RESULT_JSON" 2>/dev/null || true
|
|
fail "réponse JSON invalide renvoyée par la cible ${TARGET}"
|
|
}
|
|
|
|
if [[ "$REMOTE_STATUS" != "success" ]]; then
|
|
cat "$REMOTE_RESULT_JSON"
|
|
fail "restauration distante échouée pour la cible ${TARGET}"
|
|
fi
|
|
|
|
python3 - <<'PY' "$REMOTE_RESULT_JSON"
|
|
import json, sys
|
|
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
message = data.get("message", "restauration terminée")
|
|
environment = data.get("environment") or "N/A"
|
|
database = data.get("database") or "N/A"
|
|
request_id = data.get("request_id") or "N/A"
|
|
dump_file = data.get("dump_file") or "N/A"
|
|
log_file = data.get("log_file") or "N/A"
|
|
|
|
print(f"[{request_id}] {message}")
|
|
print(f"Environnement : {environment}")
|
|
print(f"Base : {database}")
|
|
print(f"Dump : {dump_file}")
|
|
print(f"Log : {log_file}")
|
|
PY
|