bf1efb2099
- 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>
202 lines
6.6 KiB
Bash
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"
|
|
}
|