Rediseñar auth a login de suscripción y corregir bugs de la prueba e2e
Auth: ANTHROPIC_API_KEY_FILE queda vacío por defecto — claude -p usa el login de suscripción ya activo (~/.claude/.credentials.json) en vez de una API key root-only. Se descartó ese diseño en vivo al confirmar que no había API key real disponible; además elimina la necesidad de crear secretos root-only vía sudo. Consecuencia: el futuro servicio systemd debe correr como devops, no root. CLAUDE_BIN/KUBECTL_BIN se agregan por ruta absoluta (ninguno está en el PATH de root/systemd). Bugs encontrados y corregidos durante la prueba end-to-end contra el cluster real (deployment canario desechable): - execute_corrective_action se invocaba en un subshell ($()), perdiendo sus variables de resultado (LAST_ACTION_*) -> crash "unbound variable". Ahora usa el exit status de la función, invocada directo. - DRY_RUN marcaba blocked=true en state.json como si la acción hubiera sido real, contaminando una corrida real posterior sobre el mismo recurso. Ahora el estado solo se actualiza fuera de DRY_RUN. También: GITEA_TOKEN_FILE pasa a /DATA (dueño devops, sin sudo), y media.cruzcloud.net se excluye de PUBLIC_DOMAINS (uso personal, no es parte del lab). Prueba e2e completa documentada en apps-registry: docs/arquitectura/agente-monitoreo.md.
This commit is contained in:
1 parent
5c1cb3aa5e
commit
ad53cff1e7
3 files changed
+100
-66
No files matched your search
+38
-41
@@ -1,17 +1,17 @@
|
|||||||
# lab-agent — agente de diagnóstico/corrección del lab (Paso 2)
|
# lab-agent — agente de diagnóstico/corrección del lab
|
||||||
|
|
||||||
Wrapper headless que corre `claude -p` para diagnosticar el estado del
|
Wrapper headless que corre `claude -p` para diagnosticar el estado del
|
||||||
lab (kubectl, docker, curl a endpoints públicos, API de Gitea Actions) y,
|
lab (kubectl, docker, curl a endpoints públicos, API de Gitea Actions) y,
|
||||||
solo para un conjunto explícito y aprobado de recursos, ejecutar una
|
solo para un conjunto explícito y aprobado de recursos, ejecutar una
|
||||||
acción correctiva de bajo riesgo. Ver la lista blanca completa y su
|
acción correctiva de bajo riesgo. Ver la lista blanca completa, la
|
||||||
justificación de gobernanza en
|
justificación de gobernanza y la evidencia de la prueba end-to-end en
|
||||||
`apps-registry` → `docs/arquitectura/agente-monitoreo.md` (Paso 6,
|
`apps-registry` → `docs/arquitectura/agente-monitoreo.md`.
|
||||||
pendiente de escribir).
|
|
||||||
|
|
||||||
Este documento cubre solo el Paso 2 (el script). Los Pasos 3
|
**Estado (2026-08-26): script y prueba end-to-end validados en vivo**
|
||||||
(notificaciones), 4 (systemd timer) y 5 (prueba controlada) son
|
contra el cluster real (deployment canario desechable, ciclo completo
|
||||||
posteriores y requieren tu confirmación explícita antes de activarse en
|
DRY_RUN → ejecución real → rate limit/bloqueo → limpieza). El timer de
|
||||||
continuo — ver instrucciones al final.
|
systemd (Paso 4) **todavía no está activo** — pendiente de tu
|
||||||
|
confirmación explícita.
|
||||||
|
|
||||||
## Diseño de seguridad (por qué Claude no ejecuta comandos directamente)
|
## Diseño de seguridad (por qué Claude no ejecuta comandos directamente)
|
||||||
|
|
||||||
@@ -98,45 +98,42 @@ cp lab-agent.env.example lab-agent.env
|
|||||||
# host distinto termine commiteada)
|
# host distinto termine commiteada)
|
||||||
```
|
```
|
||||||
|
|
||||||
Dos archivos root-only que este script espera pero **no crea** (por
|
**Autenticación de `claude -p`:** `ANTHROPIC_API_KEY_FILE` queda vacío
|
||||||
diseño — la creación de credenciales no se hace desde una sesión
|
por defecto — el script usa el login de suscripción ya activo del
|
||||||
interactiva de Claude Code en este lab, ver política del proyecto):
|
usuario que lo corre (`~/.claude/.credentials.json`), no una API key
|
||||||
|
aparte. Por eso el servicio systemd (cuando se active) debe correr
|
||||||
|
como `devops`, no como `root`. Ver la sección "Autenticación" en
|
||||||
|
`docs/arquitectura/agente-monitoreo.md` para el porqué completo.
|
||||||
|
|
||||||
|
El único secreto real es `GITEA_TOKEN_FILE` (opcional, token de Gitea
|
||||||
|
Actions con scope lectura) — dueño `devops`, sin sudo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir -p /etc/claude-lab-agent/secrets
|
mkdir -p "$(dirname "$GITEA_TOKEN_FILE")" # por defecto: /DATA/AppData/k3d-lab/lab-agent/secrets/
|
||||||
sudo tee /etc/claude-lab-agent/secrets/anthropic_api_key >/dev/null # pega la key, Ctrl-D
|
echo "<tu-token>" > "$GITEA_TOKEN_FILE"
|
||||||
sudo tee /etc/claude-lab-agent/secrets/gitea_token >/dev/null # token con scope read:repository (actions)
|
chmod 600 "$GITEA_TOKEN_FILE"
|
||||||
sudo chown root:root /etc/claude-lab-agent/secrets/*
|
|
||||||
sudo chmod 600 /etc/claude-lab-agent/secrets/*
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`GITEA_TOKEN_FILE` es opcional: si no existe, el agente sigue
|
Si no existe, el agente sigue funcionando pero pierde la señal de
|
||||||
funcionando pero pierde la señal de "runner atascado" vía API de Gitea
|
"runner atascado" vía API de Gitea Actions (solo detecta el contenedor
|
||||||
Actions (solo detecta el contenedor caído/reiniciando).
|
caído/reiniciando).
|
||||||
|
|
||||||
## Probar (Paso 2 — humo, sin secretos)
|
## Probar
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./lab-agent.sh --dry-run
|
./lab-agent.sh --dry-run # simulación, no toca el cluster
|
||||||
|
./lab-agent.sh --live # ejecución real
|
||||||
```
|
```
|
||||||
|
|
||||||
Ya validado en vivo 2026-08-25 hasta el paso de invocar `claude -p`
|
Validado en vivo 2026-08-26 con un deployment canario desechable
|
||||||
(falla ahí, correctamente, por falta de
|
(`test-agent-canary`, `busybox:stable` con `exit 1` — CrashLoopBackOff
|
||||||
`ANTHROPIC_API_KEY_FILE` — comportamiento esperado sin el secreto
|
garantizado): DRY_RUN detecta y "ejecuta" sin tocar el cluster, `--live`
|
||||||
configurado). El diagnóstico de los 3 deployments de `ecommerce`,
|
ejecuta de verdad (confirmado con `kubectl get events` y la anotación
|
||||||
`gitea-runner` y los 5 dominios públicos funcionó correctamente contra
|
`restartedAt`), y una segunda corrida sobre el mismo recurso caído
|
||||||
el cluster real.
|
respeta el bloqueo (`notify_only`, sin reintento). Dos bugs reales se
|
||||||
|
encontraron y corrigieron en esa prueba — ver el detalle en
|
||||||
|
`docs/arquitectura/agente-monitoreo.md`.
|
||||||
|
|
||||||
Con los secretos configurados, `--dry-run` corre el flujo completo
|
**No actives el timer de systemd (Paso 4) todavía** — requiere tu
|
||||||
(incluida la llamada real a `claude -p`, que sí tiene costo) pero
|
confirmación explícita por separado, aunque la prueba end-to-end ya
|
||||||
sustituye la ejecución de `kubectl rollout restart`/`docker restart`
|
pasó.
|
||||||
por un mensaje `[DRY_RUN]` — no toca el cluster ni el contenedor de
|
|
||||||
verdad. `DRY_RUN=1` es el valor por defecto en `lab-agent.env.example`;
|
|
||||||
pasar `--live` (o `DRY_RUN=0` en el env) para el modo real.
|
|
||||||
|
|
||||||
**No actives el timer de systemd (Paso 4) todavía.** Falta:
|
|
||||||
Paso 3 (canal de notificación real), Paso 5 (prueba controlada con un
|
|
||||||
escenario real de CrashLoopBackOff inducido, mostrándote los logs antes
|
|
||||||
de habilitar el timer en continuo), y Paso 6 (documentar todo esto en
|
|
||||||
docs-portal). Este script se puede correr manualmente para más pruebas
|
|
||||||
de humo mientras tanto.
|
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
# Copia este archivo a lab-agent.env (sin .example) y ajusta valores.
|
# Copia este archivo a lab-agent.env (sin .example) y ajusta valores.
|
||||||
# lab-agent.env NO debe contener secretos — API keys y tokens van en
|
# lab-agent.env NO debe contener secretos — lo único sensible es el token
|
||||||
# archivos aparte, root-only (ver ANTHROPIC_API_KEY_FILE / GITEA_TOKEN_FILE).
|
# de Gitea Actions, que va en un archivo aparte (ver GITEA_TOKEN_FILE).
|
||||||
|
|
||||||
# --- Alcance kubectl ---
|
# --- Alcance kubectl ---
|
||||||
NAMESPACE=ecommerce
|
NAMESPACE=ecommerce
|
||||||
KUBECONFIG_PATH=/DATA/.kube/config
|
KUBECONFIG_PATH=/DATA/.kube/config
|
||||||
|
|
||||||
|
# claude y kubectl viven bajo el HOME de devops, no en un PATH estándar de
|
||||||
|
# root/systemd (confirmado en vivo 2026-08-26: `sudo which claude` no lo
|
||||||
|
# encuentra). Rutas absolutas, no confiar en PATH.
|
||||||
|
CLAUDE_BIN=/DATA/AppData/home-devops/.local/bin/claude
|
||||||
|
KUBECTL_BIN=/DATA/AppData/home-devops/.local/bin/kubectl
|
||||||
|
|
||||||
# --- Umbrales (lista blanca aprobada 2026-08-25) ---
|
# --- Umbrales (lista blanca aprobada 2026-08-25) ---
|
||||||
UNHEALTHY_THRESHOLD_MIN=10
|
UNHEALTHY_THRESHOLD_MIN=10
|
||||||
RATE_LIMIT_MIN=30
|
RATE_LIMIT_MIN=30
|
||||||
@@ -32,15 +38,25 @@ DOCKER_RESTART_ALLOWLIST=gitea-runner
|
|||||||
GITEA_ACTIONS_REPOS="devops/apps-registry"
|
GITEA_ACTIONS_REPOS="devops/apps-registry"
|
||||||
|
|
||||||
# --- Endpoints públicos a chequear (solo diagnóstico, no gatillan acciones) ---
|
# --- Endpoints públicos a chequear (solo diagnóstico, no gatillan acciones) ---
|
||||||
PUBLIC_DOMAINS="shop.cruzcloud.net commerce.cruzcloud.net media.cruzcloud.net gitea.cruzcloud.net docs.cruzcloud.net"
|
# media.cruzcloud.net excluido a propósito: no es parte del lab, es uso
|
||||||
|
# personal aparte (confirmado 2026-08-26).
|
||||||
|
PUBLIC_DOMAINS="shop.cruzcloud.net commerce.cruzcloud.net gitea.cruzcloud.net docs.cruzcloud.net"
|
||||||
|
|
||||||
# --- Rutas de estado/logs (escribibles por devops, NO root-only) ---
|
# --- Rutas de estado/logs (escribibles por devops, NO root-only) ---
|
||||||
STATE_FILE=/DATA/AppData/k3d-lab/lab-agent/state.json
|
STATE_FILE=/DATA/AppData/k3d-lab/lab-agent/state.json
|
||||||
LOG_DIR=/DATA/AppData/k3d-lab/lab-agent/logs
|
LOG_DIR=/DATA/AppData/k3d-lab/lab-agent/logs
|
||||||
|
|
||||||
# --- Secretos root-only (archivos, NO valores — ver README.md) ---
|
# --- Autenticación de claude -p ---
|
||||||
ANTHROPIC_API_KEY_FILE=/etc/claude-lab-agent/secrets/anthropic_api_key
|
# Vacío (default): usa el login de suscripción ya activo para el usuario
|
||||||
GITEA_TOKEN_FILE=/etc/claude-lab-agent/secrets/gitea_token
|
# que corre el script (~/.claude/.credentials.json) — por eso el servicio
|
||||||
|
# systemd del Paso 4 corre como `devops`, no como root: root no tiene ese
|
||||||
|
# login. Solo completar esta ruta si en algún momento se prefiere
|
||||||
|
# autenticar con una API key real en vez del login de suscripción.
|
||||||
|
ANTHROPIC_API_KEY_FILE=
|
||||||
|
|
||||||
|
# Token de Gitea Actions (opcional, scope read:repository). Dueño devops,
|
||||||
|
# 600 — ya no root-only, no hace falta sudo para crearlo.
|
||||||
|
GITEA_TOKEN_FILE=/DATA/AppData/k3d-lab/lab-agent/secrets/gitea_token
|
||||||
|
|
||||||
# --- Modelo e invocación de Claude Code headless ---
|
# --- Modelo e invocación de Claude Code headless ---
|
||||||
CLAUDE_MODEL=claude-sonnet-5
|
CLAUDE_MODEL=claude-sonnet-5
|
||||||
|
|||||||
Regular → Executable
+40
-19
@@ -26,6 +26,11 @@ source "$ENV_FILE"
|
|||||||
|
|
||||||
: "${NAMESPACE:=ecommerce}"
|
: "${NAMESPACE:=ecommerce}"
|
||||||
: "${KUBECONFIG_PATH:=/DATA/.kube/config}"
|
: "${KUBECONFIG_PATH:=/DATA/.kube/config}"
|
||||||
|
# claude y kubectl viven bajo /DATA/AppData/home-devops/.local/bin, que NO
|
||||||
|
# está en el PATH de root ni del futuro servicio systemd (confirmado en
|
||||||
|
# vivo 2026-08-26) — por eso van por ruta explícita, no por búsqueda en PATH.
|
||||||
|
: "${CLAUDE_BIN:=claude}"
|
||||||
|
: "${KUBECTL_BIN:=kubectl}"
|
||||||
: "${UNHEALTHY_THRESHOLD_MIN:=10}"
|
: "${UNHEALTHY_THRESHOLD_MIN:=10}"
|
||||||
: "${RATE_LIMIT_MIN:=30}"
|
: "${RATE_LIMIT_MIN:=30}"
|
||||||
: "${COMMERCE_BACKEND_DEPLOYMENT:=medusa-deploy}"
|
: "${COMMERCE_BACKEND_DEPLOYMENT:=medusa-deploy}"
|
||||||
@@ -34,11 +39,16 @@ source "$ENV_FILE"
|
|||||||
: "${DOCKER_RESTART_ALLOWLIST:=gitea-runner}"
|
: "${DOCKER_RESTART_ALLOWLIST:=gitea-runner}"
|
||||||
: "${GITEA_ACTIONS_REPOS:=devops/apps-registry}"
|
: "${GITEA_ACTIONS_REPOS:=devops/apps-registry}"
|
||||||
: "${GITEA_API_BASE:=https://gitea.cruzcloud.net/api/v1}"
|
: "${GITEA_API_BASE:=https://gitea.cruzcloud.net/api/v1}"
|
||||||
: "${PUBLIC_DOMAINS:=shop.cruzcloud.net commerce.cruzcloud.net media.cruzcloud.net gitea.cruzcloud.net docs.cruzcloud.net}"
|
: "${PUBLIC_DOMAINS:=shop.cruzcloud.net commerce.cruzcloud.net gitea.cruzcloud.net docs.cruzcloud.net}"
|
||||||
: "${STATE_FILE:=/DATA/AppData/k3d-lab/lab-agent/state.json}"
|
: "${STATE_FILE:=/DATA/AppData/k3d-lab/lab-agent/state.json}"
|
||||||
: "${LOG_DIR:=/DATA/AppData/k3d-lab/lab-agent/logs}"
|
: "${LOG_DIR:=/DATA/AppData/k3d-lab/lab-agent/logs}"
|
||||||
: "${ANTHROPIC_API_KEY_FILE:=/etc/claude-lab-agent/secrets/anthropic_api_key}"
|
# Vacío por defecto: sin ANTHROPIC_API_KEY_FILE, claude -p usa el login de
|
||||||
: "${GITEA_TOKEN_FILE:=/etc/claude-lab-agent/secrets/gitea_token}"
|
# suscripción ya activo para el usuario que corre este script (el mismo
|
||||||
|
# ~/.claude/.credentials.json de una sesión interactiva normal) — no hace
|
||||||
|
# falta una API key aparte. Solo configurar esto si en algún momento se
|
||||||
|
# quiere autenticar con una API key real en vez del login de suscripción.
|
||||||
|
: "${ANTHROPIC_API_KEY_FILE:=}"
|
||||||
|
: "${GITEA_TOKEN_FILE:=/DATA/AppData/k3d-lab/lab-agent/secrets/gitea_token}"
|
||||||
: "${CLAUDE_MODEL:=claude-sonnet-5}"
|
: "${CLAUDE_MODEL:=claude-sonnet-5}"
|
||||||
: "${CLAUDE_MAX_BUDGET_USD:=0.50}"
|
: "${CLAUDE_MAX_BUDGET_USD:=0.50}"
|
||||||
: "${NTFY_URL:=}"
|
: "${NTFY_URL:=}"
|
||||||
@@ -67,7 +77,7 @@ trap 'log "ERROR inesperado en línea $LINENO (comando: $BASH_COMMAND)"' ERR
|
|||||||
|
|
||||||
log "=== lab-agent inicio (DRY_RUN=${DRY_RUN}) ==="
|
log "=== lab-agent inicio (DRY_RUN=${DRY_RUN}) ==="
|
||||||
|
|
||||||
for c in kubectl docker curl jq flock claude date; do
|
for c in "$KUBECTL_BIN" docker curl jq flock "$CLAUDE_BIN" date; do
|
||||||
command -v "$c" >/dev/null 2>&1 || { log "FATAL: falta comando requerido '$c'"; exit 1; }
|
command -v "$c" >/dev/null 2>&1 || { log "FATAL: falta comando requerido '$c'"; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -145,7 +155,7 @@ notify_send() { # title message
|
|||||||
# Diagnóstico: deployments de ecommerce
|
# Diagnóstico: deployments de ecommerce
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
DEPLOY_JSON="$(kubectl -n "$NAMESPACE" get deployment -o json 2>/dev/null)" \
|
DEPLOY_JSON="$("$KUBECTL_BIN" -n "$NAMESPACE" get deployment -o json 2>/dev/null)" \
|
||||||
|| { log "FATAL: no se pudo consultar deployments en namespace ${NAMESPACE}"; exit 1; }
|
|| { log "FATAL: no se pudo consultar deployments en namespace ${NAMESPACE}"; exit 1; }
|
||||||
|
|
||||||
mapfile -t DETECTED_DEPLOYMENTS < <(jq -r '.items[].metadata.name' <<<"$DEPLOY_JSON")
|
mapfile -t DETECTED_DEPLOYMENTS < <(jq -r '.items[].metadata.name' <<<"$DEPLOY_JSON")
|
||||||
@@ -328,7 +338,7 @@ log "elegibilidad ${GITEA_KEY}: ${GITEA_ELIG}"
|
|||||||
# Diagnóstico: contexto adicional (no gatilla acciones, solo informativo)
|
# Diagnóstico: contexto adicional (no gatilla acciones, solo informativo)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
CLUSTER_WIDE_SNAPSHOT="$(kubectl get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount,READY:.status.containerStatuses[0].ready' 2>/dev/null || echo "no disponible")"
|
CLUSTER_WIDE_SNAPSHOT="$("$KUBECTL_BIN" get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount,READY:.status.containerStatuses[0].ready' 2>/dev/null || echo "no disponible")"
|
||||||
|
|
||||||
PUBLIC_CHECKS=""
|
PUBLIC_CHECKS=""
|
||||||
for d in $PUBLIC_DOMAINS; do
|
for d in $PUBLIC_DOMAINS; do
|
||||||
@@ -429,15 +439,22 @@ EOF
|
|||||||
# Invocar claude -p (sin herramientas, salida estructurada)
|
# Invocar claude -p (sin herramientas, salida estructurada)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
CLAUDE_AUTH_ENV=()
|
||||||
|
if [[ -n "$ANTHROPIC_API_KEY_FILE" ]]; then
|
||||||
if [[ ! -r "$ANTHROPIC_API_KEY_FILE" ]]; then
|
if [[ ! -r "$ANTHROPIC_API_KEY_FILE" ]]; then
|
||||||
log "FATAL: no se puede leer ${ANTHROPIC_API_KEY_FILE} (ver README.md para crearlo, root-only)"
|
log "FATAL: ANTHROPIC_API_KEY_FILE está configurado (${ANTHROPIC_API_KEY_FILE}) pero no se puede leer"
|
||||||
notify_send "lab-agent: error de configuración" "Falta o no es legible ${ANTHROPIC_API_KEY_FILE}."
|
notify_send "lab-agent: error de configuración" "Falta o no es legible ${ANTHROPIC_API_KEY_FILE}."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
CLAUDE_AUTH_ENV=("ANTHROPIC_API_KEY=$(<"$ANTHROPIC_API_KEY_FILE")")
|
||||||
|
log "usando ANTHROPIC_API_KEY_FILE (${ANTHROPIC_API_KEY_FILE})"
|
||||||
|
else
|
||||||
|
log "ANTHROPIC_API_KEY_FILE no configurado — usando el login de suscripción ya activo para este usuario"
|
||||||
|
fi
|
||||||
|
|
||||||
CLAUDE_JSON="$(
|
CLAUDE_JSON="$(
|
||||||
ANTHROPIC_API_KEY="$(<"$ANTHROPIC_API_KEY_FILE")" \
|
env "${CLAUDE_AUTH_ENV[@]}" \
|
||||||
claude -p "$PROMPT" \
|
"$CLAUDE_BIN" -p "$PROMPT" \
|
||||||
--model "$CLAUDE_MODEL" \
|
--model "$CLAUDE_MODEL" \
|
||||||
--allowedTools "" \
|
--allowedTools "" \
|
||||||
--disallowedTools "Bash,Read,Write,Edit,WebFetch,WebSearch" \
|
--disallowedTools "Bash,Read,Write,Edit,WebFetch,WebSearch" \
|
||||||
@@ -496,7 +513,7 @@ run_and_capture() { # cmd... -> setea LAST_ACTION_STDOUT/STDERR, devuelve el exi
|
|||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
|
|
||||||
execute_corrective_action() { # resource -> imprime "ok"|"fail" y deja stdout/stderr en variables globales
|
execute_corrective_action() { # resource -> exit 0=ok/1=fail, deja LAST_ACTION_* en variables globales
|
||||||
local resource="$1"
|
local resource="$1"
|
||||||
LAST_ACTION_STDOUT=""
|
LAST_ACTION_STDOUT=""
|
||||||
LAST_ACTION_STDERR=""
|
LAST_ACTION_STDERR=""
|
||||||
@@ -507,9 +524,9 @@ execute_corrective_action() { # resource -> imprime "ok"|"fail" y deja stdout/st
|
|||||||
LAST_ACTION_CMD="docker restart gitea-runner"
|
LAST_ACTION_CMD="docker restart gitea-runner"
|
||||||
if [[ "$DRY_RUN" == "1" ]]; then
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
LAST_ACTION_STDOUT="[DRY_RUN] no se ejecutó de verdad"
|
LAST_ACTION_STDOUT="[DRY_RUN] no se ejecutó de verdad"
|
||||||
echo "ok"; return
|
return 0
|
||||||
fi
|
fi
|
||||||
if run_and_capture docker restart gitea-runner; then echo "ok"; else echo "fail"; fi
|
if run_and_capture docker restart gitea-runner; then return 0; else return 1; fi
|
||||||
;;
|
;;
|
||||||
deploy:*)
|
deploy:*)
|
||||||
local dep="${resource#deploy:}"
|
local dep="${resource#deploy:}"
|
||||||
@@ -517,18 +534,18 @@ execute_corrective_action() { # resource -> imprime "ok"|"fail" y deja stdout/st
|
|||||||
for d in "${DETECTED_DEPLOYMENTS[@]}"; do [[ "$d" == "$dep" ]] && found=1; done
|
for d in "${DETECTED_DEPLOYMENTS[@]}"; do [[ "$d" == "$dep" ]] && found=1; done
|
||||||
if [[ "$found" != "1" ]]; then
|
if [[ "$found" != "1" ]]; then
|
||||||
LAST_ACTION_STDERR="deployment '${dep}' no está entre los detectados este ciclo — rechazado"
|
LAST_ACTION_STDERR="deployment '${dep}' no está entre los detectados este ciclo — rechazado"
|
||||||
echo "fail"; return
|
return 1
|
||||||
fi
|
fi
|
||||||
LAST_ACTION_CMD="kubectl -n ${NAMESPACE} rollout restart deployment/${dep}"
|
LAST_ACTION_CMD="kubectl -n ${NAMESPACE} rollout restart deployment/${dep}"
|
||||||
if [[ "$DRY_RUN" == "1" ]]; then
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
LAST_ACTION_STDOUT="[DRY_RUN] no se ejecutó de verdad"
|
LAST_ACTION_STDOUT="[DRY_RUN] no se ejecutó de verdad"
|
||||||
echo "ok"; return
|
return 0
|
||||||
fi
|
fi
|
||||||
if run_and_capture kubectl -n "$NAMESPACE" rollout restart "deployment/${dep}"; then echo "ok"; else echo "fail"; fi
|
if run_and_capture "$KUBECTL_BIN" -n "$NAMESPACE" rollout restart "deployment/${dep}"; then return 0; else return 1; fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
LAST_ACTION_STDERR="recurso desconocido: ${resource}"
|
LAST_ACTION_STDERR="recurso desconocido: ${resource}"
|
||||||
echo "fail"
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -552,12 +569,16 @@ while IFS= read -r action_json; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$decision" == "restart" ]]; then
|
if [[ "$decision" == "restart" ]]; then
|
||||||
result="$(execute_corrective_action "$resource")"
|
if execute_corrective_action "$resource"; then result="ok"; else result="fail"; fi
|
||||||
ACTIONS_TAKEN=1
|
ACTIONS_TAKEN=1
|
||||||
|
|
||||||
# Tras cualquier intento (éxito o fallo), marcar blocked=true: un solo
|
# Tras cualquier intento REAL (éxito o fallo), marcar blocked=true: un
|
||||||
# intento automático por incidente, sin reintentos silenciosos.
|
# solo intento automático por incidente, sin reintentos silenciosos.
|
||||||
|
# En DRY_RUN no se toca el estado — es una simulación, no debe consumir
|
||||||
|
# el "único intento" ni el rate limit de una corrida real posterior.
|
||||||
|
if [[ "$DRY_RUN" != "1" ]]; then
|
||||||
mark_action_taken "$resource"
|
mark_action_taken "$resource"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$result" == "ok" ]]; then
|
if [[ "$result" == "ok" ]]; then
|
||||||
log "ACCIÓN EJECUTADA: ${LAST_ACTION_CMD} -> OK"
|
log "ACCIÓN EJECUTADA: ${LAST_ACTION_CMD} -> OK"
|
||||||
|
|||||||
Reference in new issue
Block a user