Files
fn_registry/bash/functions/shell/run_steps.sh
T
egutierrez bf1efb2099 feat: externalize apps/analysis to Gitea repos, add analysis table
- Migration 007: repo_url on apps table + analysis table with FTS5
- Analysis struct, parser, CRUD, validation, hash computation
- Selective purge: remote-only apps/analysis preserved across fn index
- CLI: fn app list/clone/pull, fn analysis list/clone/pull
- search/show/list now include analysis results
- Apps removed from git tracking (content lives in Gitea repos)
- .gitkeep for apps/ and analysis/ dirs
- Bash functions: jupyter analysis pipeline, shell utilities
- Browser domain: CDP functions moved from infra to browser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 04:23:51 +02:00

202 lines
6.6 KiB
Bash

#!/usr/bin/env bash
# run_steps — ejecuta pasos de un YAML generico (action=command)
# run_steps <yaml_file> [--strict]
#
# Lee un YAML con la estructura:
# name: <run_name>
# steps:
# - name: <step_name>
# action: command
# command: "<shell_command>"
# continue_on_error: true|false # opcional, default false
# timeout_ms: 30000 # opcional, default 30000
#
# Para cada paso de action=command:
# - Ejecuta el command con timeout (timeout_ms ms)
# - Captura exit code, stdout+stderr y elapsed time
# - Si falla y continue_on_error=false → aborta
# - Acumula resultados en TSV temporal
#
# Al final:
# - Llama a report_execution_json para generar JSON a stdout
# - Llama a exit_with_status para determinar el exit code
#
# --strict: mapea partial (2) a failure (1) en status y exit code
#
# Requiere: yq, jq (jq opcional si report_execution_json tiene fallback printf)
run_steps() {
local yaml_file="$1"
local strict=0
if [[ "${2:-}" == "--strict" ]]; then
strict=1
fi
# --- validaciones previas ---
if [[ -z "$yaml_file" ]]; then
echo "run_steps: yaml_file requerido" >&2
return 1
fi
if [[ ! -f "$yaml_file" ]]; then
echo "run_steps: archivo '$yaml_file' no existe" >&2
return 1
fi
if ! command -v yq &>/dev/null; then
echo "run_steps: yq no encontrado en PATH" >&2
return 1
fi
# --- cargar funciones del estandar ---
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=bash/functions/shell/exit_with_status.sh
source "$script_dir/exit_with_status.sh"
# shellcheck source=bash/functions/shell/report_execution_json.sh
source "$script_dir/report_execution_json.sh"
# --- leer nombre del run ---
local run_name
run_name=$(yq e '.name // "unnamed"' "$yaml_file")
# --- contar pasos ---
local step_count
step_count=$(yq e '.steps | length' "$yaml_file")
if [[ -z "$step_count" || "$step_count" -eq 0 ]]; then
echo "run_steps: el YAML no tiene steps" >&2
return 1
fi
# --- archivo TSV temporal para acumular resultados ---
# columnas: name<TAB>action<TAB>status<TAB>elapsed_ms<TAB>output<TAB>error
local tsv_file
tsv_file=$(mktemp /tmp/run_steps_XXXXXX.tsv)
# shellcheck disable=SC2064
trap "rm -f '$tsv_file'" EXIT
local ok_steps=0
local failed_steps=0
local started_at
started_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local t_run_start
t_run_start=$(date +%s%3N)
# --- iterar sobre cada paso ---
local i
for (( i=0; i<step_count; i++ )); do
local step_name step_action step_command step_continue step_timeout_ms
step_name=$(yq e ".steps[$i].name // \"step_$i\"" "$yaml_file")
step_action=$(yq e ".steps[$i].action // \"command\"" "$yaml_file")
step_command=$(yq e ".steps[$i].command // \"\"" "$yaml_file")
step_continue=$(yq e ".steps[$i].continue_on_error // false" "$yaml_file")
step_timeout_ms=$(yq e ".steps[$i].timeout_ms // 30000" "$yaml_file")
# convertir timeout de ms a segundos enteros (timeout(1) usa segundos)
local timeout_sec
timeout_sec=$(( (step_timeout_ms + 999) / 1000 ))
# solo soportamos action=command
if [[ "$step_action" != "command" ]]; then
local err_msg="action '$step_action' no soportada (solo command)"
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
"$step_name" "$step_action" "error" "0" "" "$err_msg" >> "$tsv_file"
failed_steps=$((failed_steps + 1))
if [[ "$step_continue" != "true" ]]; then
echo "run_steps: paso '$step_name' — $err_msg — abortando" >&2
break
fi
continue
fi
if [[ -z "$step_command" ]]; then
local err_msg="campo 'command' vacio en paso '$step_name'"
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
"$step_name" "command" "error" "0" "" "$err_msg" >> "$tsv_file"
failed_steps=$((failed_steps + 1))
if [[ "$step_continue" != "true" ]]; then
echo "run_steps: $err_msg — abortando" >&2
break
fi
continue
fi
# ejecutar con timeout y capturar output + tiempos
local t_start t_end elapsed_ms step_output step_exit
t_start=$(date +%s%3N)
step_output=$(timeout "$timeout_sec" bash -c "$step_command" 2>&1)
step_exit=$?
t_end=$(date +%s%3N)
elapsed_ms=$(( t_end - t_start ))
# timeout(1) sale con 124 cuando agota el tiempo
local step_error=""
if [[ "$step_exit" -eq 124 ]]; then
step_error="timeout: comando excedio ${step_timeout_ms}ms"
fi
# escapar tabuladores y saltos de linea en el output para el TSV
local safe_output
safe_output=$(printf '%s' "$step_output" | tr '\t' ' ' | tr '\n' ' ')
if [[ "$step_exit" -eq 0 ]]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
"$step_name" "command" "ok" "$elapsed_ms" "$safe_output" "" >> "$tsv_file"
ok_steps=$((ok_steps + 1))
else
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
"$step_name" "command" "error" "$elapsed_ms" "$safe_output" "$step_error" >> "$tsv_file"
failed_steps=$((failed_steps + 1))
if [[ "$step_continue" != "true" ]]; then
echo "run_steps: paso '$step_name' fallo (exit $step_exit) — abortando" >&2
break
fi
fi
done
local t_run_end
t_run_end=$(date +%s%3N)
local total_duration_ms=$(( t_run_end - t_run_start ))
local ended_at
ended_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local total_steps=$(( ok_steps + failed_steps ))
# --- calcular status y exit code ---
local status_code
status_code=$(exit_with_status "$total_steps" "$ok_steps" "$failed_steps")
local run_status
case "$status_code" in
0) run_status="success" ;;
1) run_status="failure" ;;
2)
if [[ "$strict" -eq 1 ]]; then
run_status="failure"
status_code=1
else
run_status="partial"
fi
;;
*) run_status="failure"; status_code=1 ;;
esac
# --- generar JSON a stdout ---
report_execution_json \
"$run_name" \
"$run_status" \
"$status_code" \
"$started_at" \
"$ended_at" \
"$total_duration_ms" \
"$tsv_file"
return "$status_code"
}