#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════════
# PARSE-YAML.SH - Parser YAML simple pour conf.gouroubleu.yml
# Usage: parse_yaml fichier.yml "PREFIX_"
# Crée des variables bash : PREFIX_name, PREFIX_target, PREFIX_port, etc.
# ═══════════════════════════════════════════════════════════════════════════
parse_yaml() {
local yaml_file="$1"
local prefix="${2:-}"
if [[ ! -f "$yaml_file" ]]; then
echo "❌ Fichier YAML non trouvé: $yaml_file" >&2
return 1
fi
# Parser simple avec sed/awk
# Gère les cas basiques : key: value, key: "value", key: 'value'
local s='[[:space:]]*'
local w='[a-zA-Z0-9_]*'
# Extraire les paires clé: valeur de premier niveau
while IFS= read -r line; do
# Ignorer commentaires et lignes vides
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
# Extraire clé et valeur
if [[ "$line" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
# Nettoyer la valeur
# Supprimer quotes
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
# Supprimer commentaires inline
value="${value%%#*}"
# Trim whitespace
value="${value%"${value##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
# Si la valeur est vide ou commence par un tableau/objet, on skip pour l'instant
if [[ -z "$value" ]] || [[ "$value" == "[" ]] || [[ "$value" == "{" ]]; then
continue
fi
# Convertir true/false en bash
[[ "$value" == "true" ]] && value="true"
[[ "$value" == "false" ]] && value="false"
# Exporter la variable
local var_name="${prefix}${key}"
export "$var_name"="$value"
fi
done < "$yaml_file"
return 0
}
# ───────────────────────────────────────────────────────────────────────────
# Parser avancé pour les sections imbriquées (nginx, storage, crons, etc.)
#
# Usage: parse_yaml_section fichier.yml "section" "PREFIX_"
#
# IMPORTANT: Variables créées avec format: ${PREFIX}${section}_${key}
#
# Exemple pour nginx:
# nginx:
# domain: api.33800.nowhere84.com
# private: true
#
# parse_yaml_section conf.yml "nginx" "CFG_"
# → Crée: CFG_nginx_domain, CFG_nginx_private
#
# Donc dans le code, utiliser: ${CFG_nginx_domain:-} (PAS ${CFG_domain})
# ───────────────────────────────────────────────────────────────────────────
parse_yaml_section() {
local yaml_file="$1"
local section="$2"
local prefix="${3:-}"
if [[ ! -f "$yaml_file" ]]; then
echo "❌ Fichier YAML non trouvé: $yaml_file" >&2
return 1
fi
local in_section=false
local section_indent=0
while IFS= read -r line; do
# Calculer l'indentation
local stripped="${line#"${line%%[![:space:]]*}"}"
local indent=$(( ${#line} - ${#stripped} ))
# Ignorer commentaires et lignes vides
[[ "$stripped" =~ ^# ]] && continue
[[ -z "$stripped" ]] && continue
# Détecter début de section
if [[ "$stripped" =~ ^${section}:[[:space:]]*$ ]]; then
in_section=true
section_indent=$indent
continue
fi
# Si on est dans la section
if $in_section; then
# Sortir si on revient au niveau parent
if [[ $indent -le $section_indent ]] && [[ ! "$stripped" =~ ^[[:space:]] ]]; then
in_section=false
continue
fi
# Parser les clés de la section
if [[ "$stripped" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
# Nettoyer la valeur
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
if [[ -n "$value" ]]; then
local var_name="${prefix}${section}_${key}"
export "$var_name"="$value"
fi
fi
fi
done < "$yaml_file"
return 0
}
# ───────────────────────────────────────────────────────────────────────────
# Vérifier si une clé existe et est true
# ───────────────────────────────────────────────────────────────────────────
yaml_is_true() {
local var_name="$1"
local value="${!var_name:-false}"
[[ "$value" == "true" ]]
}
# ───────────────────────────────────────────────────────────────────────────
# Obtenir une valeur avec défaut
# ───────────────────────────────────────────────────────────────────────────
yaml_get() {
local var_name="$1"
local default="${2:-}"
echo "${!var_name:-$default}"
}
# ───────────────────────────────────────────────────────────────────────────
# Parser les listes YAML simples (format "- valeur")
#
# Usage: parse_yaml_list fichier.yml "section" "sous_section"
# Retourne les éléments un par ligne sur stdout
#
# Exemple pour:
# nginx:
# public_paths:
# - /api/oauth/callback
# - /health
#
# parse_yaml_list conf.yml "nginx" "public_paths"
# → /api/oauth/callback
# → /health
# ───────────────────────────────────────────────────────────────────────────
parse_yaml_list() {
local yaml_file="$1"
local section="$2"
local key="$3"
if [[ ! -f "$yaml_file" ]]; then
return 1
fi
local in_section=false
local in_key=false
local section_indent=0
local key_indent=0
while IFS= read -r line; do
local stripped="${line#"${line%%[![:space:]]*}"}"
local indent=$(( ${#line} - ${#stripped} ))
[[ "$stripped" =~ ^# ]] && continue
[[ -z "$stripped" ]] && continue
# Detect section start
if [[ "$stripped" =~ ^${section}:[[:space:]]*$ ]]; then
in_section=true
section_indent=$indent
in_key=false
continue
fi
if $in_section; then
# Exit section if back to parent level
if [[ $indent -le $section_indent ]] && [[ ! "$stripped" =~ ^[[:space:]] ]]; then
in_section=false
in_key=false
continue
fi
# Detect key start
if [[ "$stripped" =~ ^${key}:[[:space:]]*$ ]]; then
in_key=true
key_indent=$indent
continue
fi
if $in_key; then
# Exit key if back to key level or above
if [[ $indent -le $key_indent ]] && [[ ! "$stripped" =~ ^- ]]; then
in_key=false
continue
fi
# Parse list item "- value"
if [[ "$stripped" =~ ^-[[:space:]]+(.*) ]]; then
local value="${BASH_REMATCH[1]}"
# Clean quotes
value="${value#\"}"
value="${value%\"}"
value="${value#\'}"
value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
[[ -n "$value" ]] && echo "$value"
fi
fi
fi
done < "$yaml_file"
}
# ───────────────────────────────────────────────────────────────────────────
# Parser les sidecars YAML (liste d'objets)
#
# Usage: parse_yaml_sidecars fichier.yml
#
# Exporte:
# SIDECARS_COUNT : nombre de sidecars
# SIDECAR_0_name, SIDECAR_0_image, SIDECAR_0_ports, etc.
# Les sous-listes (ports, cap_add, devices, sysctls, volumes, networks)
# sont stockées en comma-separated
# Les sous-sections env: → SIDECAR_N_env_KEY=val
#
# Exemple pour:
# sidecars:
# - name: "wireguard-sidecar"
# image: "registry.../wireguard-sidecar:latest"
# ports:
# - "5410:5410"
# cap_add:
# - NET_ADMIN
# env:
# PORT: "5410"
# health: "/health"
#
# → SIDECARS_COUNT=1
# → SIDECAR_0_name=wireguard-sidecar
# → SIDECAR_0_image=registry.../wireguard-sidecar:latest
# → SIDECAR_0_ports=5410:5410
# → SIDECAR_0_cap_add=NET_ADMIN
# → SIDECAR_0_env_PORT=5410
# → SIDECAR_0_health=/health
# ───────────────────────────────────────────────────────────────────────────
parse_yaml_sidecars() {
local yaml_file="$1"
if [[ ! -f "$yaml_file" ]]; then
echo "❌ Fichier YAML non trouvé: $yaml_file" >&2
return 1
fi
local in_sidecars=false
local sidecars_indent=0
local index=-1
local current_list_key=""
local current_list_values=""
local in_env=false
local env_indent=0
local item_indent=0
# Liste des clés qui contiennent des sous-listes
local list_keys="ports cap_add devices sysctls volumes networks"
# Fonction interne pour finaliser une liste en cours
_flush_list() {
if [[ -n "$current_list_key" ]] && [[ -n "$current_list_values" ]] && [[ $index -ge 0 ]]; then
# Supprimer le comma de tête
current_list_values="${current_list_values#,}"
export "SIDECAR_${index}_${current_list_key}=${current_list_values}"
fi
current_list_key=""
current_list_values=""
}
while IFS= read -r line; do
local stripped="${line#"${line%%[![:space:]]*}"}"
local indent=$(( ${#line} - ${#stripped} ))
# Ignorer commentaires et lignes vides
[[ "$stripped" =~ ^# ]] && continue
[[ -z "$stripped" ]] && continue
# Détecter début de section sidecars:
if [[ "$stripped" =~ ^sidecars:[[:space:]]*$ ]]; then
in_sidecars=true
sidecars_indent=$indent
continue
fi
if ! $in_sidecars; then
continue
fi
# Sortir de sidecars si on revient au niveau parent ou en dessous
if [[ $indent -le $sidecars_indent ]] && [[ ! "$stripped" =~ ^[[:space:]] ]]; then
_flush_list
in_sidecars=false
in_env=false
continue
fi
# Détecter un nouvel item de sidecar (- name: xxx)
if [[ "$stripped" =~ ^-[[:space:]]+name:[[:space:]]+(.*) ]]; then
# Finaliser la liste précédente si en cours
_flush_list
in_env=false
index=$((index + 1))
item_indent=$indent
local value="${BASH_REMATCH[1]}"
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
export "SIDECAR_${index}_name=${value}"
continue
fi
# On doit être dans un sidecar (index >= 0)
[[ $index -lt 0 ]] && continue
# Détecter si on est dans une sous-section env:
if $in_env; then
if [[ $indent -le $env_indent ]]; then
in_env=false
# Fall through pour traiter la ligne courante
else
# Parser clé: valeur dans env
if [[ "$stripped" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
export "SIDECAR_${index}_env_${key}=${value}"
fi
continue
fi
fi
# Détecter si on collecte des items de liste (- value)
if [[ -n "$current_list_key" ]]; then
if [[ "$stripped" =~ ^-[[:space:]]+(.*) ]] && [[ "$stripped" != -\ name:* ]]; then
local value="${BASH_REMATCH[1]}"
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
current_list_values="${current_list_values},${value}"
continue
else
# Plus de list items, finaliser
_flush_list
# Fall through pour traiter la ligne courante
fi
fi
# Parser les clés du sidecar
if [[ "$stripped" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
# Nettoyer la valeur
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
# Vérifier si c'est la section env:
if [[ "$key" == "env" ]] && [[ -z "$value" ]]; then
in_env=true
env_indent=$indent
continue
fi
# Vérifier si c'est une clé de liste (valeur vide = liste qui suit)
local is_list=false
for lk in $list_keys; do
if [[ "$key" == "$lk" ]]; then
is_list=true
break
fi
done
if $is_list && [[ -z "$value" ]]; then
# Début d'une sous-liste
current_list_key="$key"
current_list_values=""
continue
fi
# Clé simple avec valeur
if [[ -n "$value" ]]; then
export "SIDECAR_${index}_${key}=${value}"
fi
fi
done < "$yaml_file"
# Finaliser dernière liste si en cours
_flush_list
# Exporter le count
export SIDECARS_COUNT=$((index + 1))
return 0
}
# ───────────────────────────────────────────────────────────────────────────
# Parser les hooks YAML (liste d'objets sous hooks.pre_deploy / hooks.post_deploy)
#
# Format YAML:
# hooks:
# pre_deploy:
# - action: http
# method: POST
# url: "http://..."
# description: "Pause worker"
# - action: wait_http
# url: "http://..."
# jq_condition: '.status == "paused"'
# timeout: 60
# post_deploy:
# - action: http
# method: POST
# url: "http://..."
#
# Exports: HOOKS_pre_deploy_COUNT, HOOKS_pre_deploy_0_action, HOOKS_pre_deploy_0_url, etc.
# HOOKS_post_deploy_COUNT, HOOKS_post_deploy_0_action, etc.
# ───────────────────────────────────────────────────────────────────────────
parse_yaml_hooks() {
local yaml_file="$1"
if [[ ! -f "$yaml_file" ]]; then
return 1
fi
local in_hooks=false
local hooks_indent=0
local in_phase=false
local phase=""
local phase_indent=0
local index=-1
while IFS= read -r line; do
local stripped="${line#"${line%%[![:space:]]*}"}"
local indent=$(( ${#line} - ${#stripped} ))
# Skip comments and empty lines
[[ "$stripped" =~ ^# ]] && continue
[[ -z "$stripped" ]] && continue
# Detect hooks: section
if [[ "$stripped" =~ ^hooks:[[:space:]]*$ ]]; then
in_hooks=true
hooks_indent=$indent
continue
fi
if ! $in_hooks; then
continue
fi
# Exit hooks if back to parent level
if [[ $indent -le $hooks_indent ]] && [[ ! "$stripped" =~ ^[[:space:]] ]]; then
in_hooks=false
in_phase=false
continue
fi
# Detect phase (pre_deploy: or post_deploy:)
if [[ "$stripped" =~ ^(pre_deploy|post_deploy):[[:space:]]*$ ]]; then
# Save previous phase count
if $in_phase && [[ -n "$phase" ]]; then
export "HOOKS_${phase}_COUNT=$((index + 1))"
fi
phase="${BASH_REMATCH[1]}"
phase_indent=$indent
in_phase=true
index=-1
continue
fi
if ! $in_phase; then
continue
fi
# Exit phase if back to phase level or above
if [[ $indent -le $phase_indent ]] && [[ ! "$stripped" =~ ^- ]]; then
export "HOOKS_${phase}_COUNT=$((index + 1))"
in_phase=false
continue
fi
# Detect new hook item (- action: xxx or - key: value)
if [[ "$stripped" =~ ^-[[:space:]]+([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
index=$((index + 1))
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
# Clean value
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
export "HOOKS_${phase}_${index}_${key}=${value}"
continue
fi
# Parse key:value within a hook item (continuation lines)
if [[ $index -ge 0 ]] && [[ "$stripped" =~ ^([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*:[[:space:]]*(.*) ]]; then
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
# Clean value
value="${value#\"}" ; value="${value%\"}"
value="${value#\'}" ; value="${value%\'}"
value="${value%%#*}"
value="${value%"${value##*[![:space:]]}"}"
export "HOOKS_${phase}_${index}_${key}=${value}"
fi
done < "$yaml_file"
# Finalize last phase count
if $in_phase && [[ -n "$phase" ]]; then
export "HOOKS_${phase}_COUNT=$((index + 1))"
fi
return 0
}