feat(deploy): generic orchestrator and deploy.sh entry
**Motivations:** - Single generic orchestration in ia_dev while business logic stays in each project repo **Root causes:** - N/A (evolution) **Correctifs:** - N/A **Evolutions:** - Add orchestrator.sh (deploy.hooks.phases or fallback deploy.deploy_script_path) - Add deploy.sh <project_id> <env> [options] as canonical entry from ia_dev root - run-project-hooks.sh execs orchestrator.sh for backward compatibility - change-to-all-branches.sh and deploy-by-script-to.sh invoke orchestrator.sh when IA_PROJECT_ID is set - Document orchestration in README.md and deploy/lib/README.md **Pages affectées:** - README.md, deploy/orchestrator.sh, deploy/deploy.sh, deploy/run-project-hooks.sh, deploy/change-to-all-branches.sh, deploy/deploy-by-script-to.sh, deploy/lib/README.md
This commit is contained in:
parent
418bfb044a
commit
0a9d6e001b
@ -40,6 +40,12 @@ Tous les scripts sont invoqués depuis la **racine de ia_dev** (ce dépôt).
|
||||
|
||||
Les scripts dans `deploy/` **déploient et versionnent les projets configurés** (lecoffreio, enso, algo, etc.) dans leurs répertoires — ils ne déploient pas ia_dev. À lancer depuis la racine de ia_dev. Chaque script accepte en option un **project_id** en premier argument (ou `--project <id>` pour pousse.sh) pour cibler le projet ; sinon le projet est résolu par MAIL_TO ou AI_AGENT_TOKEN. Les chemins absolus dans `projects/<id>/conf.json` indiquent où se trouve chaque projet et ses scripts de déploiement.
|
||||
|
||||
**Orchestration générique (métier dans le dépôt cible)** :
|
||||
|
||||
- **deploy.sh** : point d’entrée `./deploy/deploy.sh <project_id> <env> [options…]` — exporte `IA_PROJECT_ID`, puis exécute **orchestrator.sh** (même contrat que les scripts ci-dessous qui invoquent l’orchestrateur lorsque `IA_PROJECT_ID` est défini).
|
||||
- **orchestrator.sh** : lit `deploy.hooks.phases` dans `conf.json` (chemins relatifs à `repository_root`) ; si `phases` est vide, exécute `deploy.deploy_script_path`. Le métier (Prisma, systemd, build distant, etc.) reste dans les scripts du projet.
|
||||
- **run-project-hooks.sh** : délègue à **orchestrator.sh** (alias pour compatibilité avec les anciennes invocations).
|
||||
|
||||
- **bump-version.sh** : lit `projects/<id>/conf.json` (version.package_json_paths, version.splash_app_name) et met à jour VERSION + package.json du **projet configuré**. Invocation : `./deploy/bump-version.sh [project_id] <version> [message]` depuis la racine de ia_dev.
|
||||
- **deploy-by-script-to.sh** : checkout branche cible (pprod|prod), sync origin, exécute `deploy/scripts_v2/deploy.sh` du **projet configuré** (chemin dans conf), puis revient sur test. Invocation : `./deploy/deploy-by-script-to.sh [project_id] <pprod|prod>`.
|
||||
- **pousse.sh** : build check, commit, push. Invocation : `./deploy/pousse.sh [project_id|--project <id>] [--remote <remote>] [--bump-version]` avec le message de commit sur STDIN.
|
||||
|
||||
@ -47,8 +47,8 @@ echo "[change-to-all-branches] Aligning branches..."
|
||||
# scripts_v2 lives in the host project's deploy/ (not necessarily under ia_dev)
|
||||
DEPLOY_SCRIPTS_V2="${PROJECT_ROOT}/deploy/scripts_v2"
|
||||
echo "[change-to-all-branches] Deploying test (--import-v1 --skipSetupHost, --no-sync-origin because we just pushed)..."
|
||||
if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_DIR}/run-project-hooks.sh" ]]; then
|
||||
"${DEPLOY_DIR}/run-project-hooks.sh" test --import-v1 --skipSetupHost --no-sync-origin
|
||||
if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_DIR}/orchestrator.sh" ]]; then
|
||||
"${DEPLOY_DIR}/orchestrator.sh" test --import-v1 --skipSetupHost --no-sync-origin
|
||||
else
|
||||
"${DEPLOY_SCRIPTS_V2}/deploy.sh" test --import-v1 --skipSetupHost --no-sync-origin
|
||||
fi
|
||||
|
||||
@ -74,8 +74,8 @@ git fetch origin
|
||||
git reset --hard "origin/${TARGET_BRANCH}"
|
||||
|
||||
echo "[deploy-by-script-to] Step 4/5: deploy ${TARGET_BRANCH} (--import-v1 --skipSetupHost)..."
|
||||
if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_IA}/run-project-hooks.sh" ]]; then
|
||||
"${DEPLOY_IA}/run-project-hooks.sh" "$TARGET_BRANCH" --import-v1 --skipSetupHost
|
||||
if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_IA}/orchestrator.sh" ]]; then
|
||||
"${DEPLOY_IA}/orchestrator.sh" "$TARGET_BRANCH" --import-v1 --skipSetupHost
|
||||
else
|
||||
deploy_script="$PROJECT_ROOT/deploy/scripts_v2/deploy.sh"
|
||||
if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "${PROJECT_CONFIG_PATH:-}" ]] && command -v jq >/dev/null 2>&1; then
|
||||
|
||||
27
deploy/deploy.sh
Executable file
27
deploy/deploy.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generic deploy entry from ia_dev: sets IA_PROJECT_ID and runs the orchestrator.
|
||||
# Business logic remains in the target repository (deploy_script_path, hooks.phases).
|
||||
# Usage (from ia_dev root): ./deploy/deploy.sh <project_id> <env> [options...]
|
||||
# Example: ./deploy/deploy.sh lecoffreio test --import-v1 --skipSetupHost
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")"
|
||||
DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)"
|
||||
IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)"
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "[deploy][ERROR] Missing arguments" >&2
|
||||
echo "Usage: $0 <project_id> <env> [options passed to each phase / fallback deploy script]" >&2
|
||||
echo "Example: $0 lecoffreio test --import-v1 --skipSetupHost" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONF="${IA_DEV_ROOT}/projects/${1}/conf.json"
|
||||
if [[ ! -f "$CONF" ]]; then
|
||||
echo "[deploy][ERROR] No conf for project '${1}': ${CONF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export IA_PROJECT_ID="$1"
|
||||
shift
|
||||
exec "$DEPLOY_DIR/orchestrator.sh" "$@"
|
||||
@ -12,3 +12,8 @@ Optional `deploy_script_tee_log_if_requested <project_root> <log_subdir>` — re
|
||||
## Policy
|
||||
|
||||
Project-specific logic (Prisma, systemd unit names, remote app layout, LeCoffre domains) stays under each repository’s `deploy/scripts_v2/`. Only transport/logging helpers live here.
|
||||
|
||||
## Orchestration (`../orchestrator.sh`, `../deploy.sh`)
|
||||
|
||||
- **`deploy.sh`** : `./deploy/deploy.sh <project_id> <env> [args]` — generic entry; business scripts live in the target repo (`deploy_script_path` / `hooks.phases`).
|
||||
- **`orchestrator.sh`** : runs phases or fallback script; **`run-project-hooks.sh`** execs it for backward compatibility.
|
||||
|
||||
74
deploy/orchestrator.sh
Executable file
74
deploy/orchestrator.sh
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generic deploy orchestrator: runs deploy.hooks.phases from projects/<id>/conf.json (paths relative to repository_root).
|
||||
# If phases is empty or missing, exec deploy.deploy_script_path with the same arguments.
|
||||
# Business logic (Prisma, systemd, remote layout) stays in each project's scripts under repository_root.
|
||||
# Usage: orchestrator.sh <env> [options passed to each phase / fallback script]
|
||||
# Requires: IA_PROJECT_ID, IA_DEV_ROOT (or re-exec from project root like change-to-all-branches).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")"
|
||||
DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)"
|
||||
IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)"
|
||||
|
||||
_ORCH_TAG="[orchestrator]"
|
||||
|
||||
if [[ -z "${IA_PROJECT_ID:-}" ]]; then
|
||||
echo "${_ORCH_TAG}[ERROR] IA_PROJECT_ID is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=../lib/project_config.sh
|
||||
source "${IA_DEV_ROOT}/lib/project_config.sh"
|
||||
# shellcheck source=../lib/project_git_root_from_conf.sh
|
||||
source "${IA_DEV_ROOT}/lib/project_git_root_from_conf.sh"
|
||||
ia_dev_resolve_project_git_root
|
||||
REPO_ROOT="${IA_PROJECT_GIT_ROOT:-}"
|
||||
if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT" ]]; then
|
||||
echo "${_ORCH_TAG}[ERROR] Could not resolve repository root for project ${IA_PROJECT_ID}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONF="${PROJECT_CONFIG_PATH:-}"
|
||||
if [[ -z "$CONF" || ! -f "$CONF" ]]; then
|
||||
echo "${_ORCH_TAG}[ERROR] Missing conf: ${CONF:-}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "${_ORCH_TAG}[ERROR] jq is required to read deploy.hooks.phases" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SECRETS_PATH_CFG="$(jq -r '.deploy.secrets_path // empty' "$CONF")"
|
||||
if [[ -n "$SECRETS_PATH_CFG" && "$SECRETS_PATH_CFG" != "null" && -d "$SECRETS_PATH_CFG" ]]; then
|
||||
export SECRETS_BASE="$SECRETS_PATH_CFG"
|
||||
export LECOFFRE_SECRETS_BASE="$SECRETS_PATH_CFG"
|
||||
fi
|
||||
|
||||
DEPLOY_SCRIPT_PATH="$(jq -r '.deploy.deploy_script_path // empty' "$CONF")"
|
||||
if [[ -z "$DEPLOY_SCRIPT_PATH" || ! -f "$DEPLOY_SCRIPT_PATH" ]]; then
|
||||
echo "${_ORCH_TAG}[ERROR] deploy.deploy_script_path missing or not a file: ${DEPLOY_SCRIPT_PATH:-}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PHASE_COUNT="$(jq '.deploy.hooks.phases // [] | length' "$CONF")"
|
||||
if [[ "$PHASE_COUNT" == "0" ]]; then
|
||||
exec bash "$DEPLOY_SCRIPT_PATH" "$@"
|
||||
fi
|
||||
|
||||
mapfile -t PHASE_SCRIPTS < <(jq -r '.deploy.hooks.phases[]? | if type == "string" then . elif type == "object" and (.run | type == "string") then .run else empty end' "$CONF")
|
||||
|
||||
if [[ ${#PHASE_SCRIPTS[@]} -eq 0 ]]; then
|
||||
exec bash "$DEPLOY_SCRIPT_PATH" "$@"
|
||||
fi
|
||||
|
||||
for rel in "${PHASE_SCRIPTS[@]}"; do
|
||||
[[ -z "$rel" ]] && continue
|
||||
phase_path="${REPO_ROOT}/${rel}"
|
||||
if [[ ! -f "$phase_path" ]]; then
|
||||
echo "${_ORCH_TAG}[ERROR] Phase script not found: ${phase_path}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${_ORCH_TAG} Running: ${rel} $*"
|
||||
bash "$phase_path" "$@"
|
||||
done
|
||||
@ -1,71 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run deploy.hooks.phases from projects/<id>/conf.json (paths relative to repository_root).
|
||||
# If phases is empty or missing, exec deploy.deploy_script_path with the same arguments.
|
||||
# Backward-compatible alias: delegates to orchestrator.sh (generic deploy orchestrator).
|
||||
# Usage: run-project-hooks.sh <env> [options passed to each phase / fallback script]
|
||||
# Requires: IA_PROJECT_ID, IA_DEV_ROOT (or re-exec from project root like change-to-all-branches).
|
||||
# Requires: IA_PROJECT_ID (set by caller or by deploy.sh).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")"
|
||||
DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)"
|
||||
IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)"
|
||||
|
||||
if [[ -z "${IA_PROJECT_ID:-}" ]]; then
|
||||
echo "[run-project-hooks][ERROR] IA_PROJECT_ID is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=../lib/project_config.sh
|
||||
source "${IA_DEV_ROOT}/lib/project_config.sh"
|
||||
# shellcheck source=../lib/project_git_root_from_conf.sh
|
||||
source "${IA_DEV_ROOT}/lib/project_git_root_from_conf.sh"
|
||||
ia_dev_resolve_project_git_root
|
||||
REPO_ROOT="${IA_PROJECT_GIT_ROOT:-}"
|
||||
if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT" ]]; then
|
||||
echo "[run-project-hooks][ERROR] Could not resolve repository root for project ${IA_PROJECT_ID}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONF="${PROJECT_CONFIG_PATH:-}"
|
||||
if [[ -z "$CONF" || ! -f "$CONF" ]]; then
|
||||
echo "[run-project-hooks][ERROR] Missing conf: ${CONF:-}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "[run-project-hooks][ERROR] jq is required to read deploy.hooks.phases" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SECRETS_PATH_CFG="$(jq -r '.deploy.secrets_path // empty' "$CONF")"
|
||||
if [[ -n "$SECRETS_PATH_CFG" && "$SECRETS_PATH_CFG" != "null" && -d "$SECRETS_PATH_CFG" ]]; then
|
||||
export SECRETS_BASE="$SECRETS_PATH_CFG"
|
||||
export LECOFFRE_SECRETS_BASE="$SECRETS_PATH_CFG"
|
||||
fi
|
||||
|
||||
DEPLOY_SCRIPT_PATH="$(jq -r '.deploy.deploy_script_path // empty' "$CONF")"
|
||||
if [[ -z "$DEPLOY_SCRIPT_PATH" || ! -f "$DEPLOY_SCRIPT_PATH" ]]; then
|
||||
echo "[run-project-hooks][ERROR] deploy.deploy_script_path missing or not a file: ${DEPLOY_SCRIPT_PATH:-}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PHASE_COUNT="$(jq '.deploy.hooks.phases // [] | length' "$CONF")"
|
||||
if [[ "$PHASE_COUNT" == "0" ]]; then
|
||||
exec bash "$DEPLOY_SCRIPT_PATH" "$@"
|
||||
fi
|
||||
|
||||
mapfile -t PHASE_SCRIPTS < <(jq -r '.deploy.hooks.phases[]? | if type == "string" then . elif type == "object" and (.run | type == "string") then .run else empty end' "$CONF")
|
||||
|
||||
if [[ ${#PHASE_SCRIPTS[@]} -eq 0 ]]; then
|
||||
exec bash "$DEPLOY_SCRIPT_PATH" "$@"
|
||||
fi
|
||||
|
||||
for rel in "${PHASE_SCRIPTS[@]}"; do
|
||||
[[ -z "$rel" ]] && continue
|
||||
phase_path="${REPO_ROOT}/${rel}"
|
||||
if [[ ! -f "$phase_path" ]]; then
|
||||
echo "[run-project-hooks][ERROR] Phase script not found: ${phase_path}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[run-project-hooks] Running: ${rel} $*"
|
||||
bash "$phase_path" "$@"
|
||||
done
|
||||
exec "$DEPLOY_DIR/orchestrator.sh" "$@"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user