#!/usr/bin/env bash # report_execution_json — Genera un JSON de reporte de ejecucion siguiendo el estandar fn-registry. # # USO (sourced): # source report_execution_json.sh # report_execution_json "backup_db" "partial" 2 \ # "2026-04-01T02:00:00Z" "2026-04-01T02:00:03Z" 3000 /tmp/steps.tsv # # USO (ejecutado directamente): # bash report_execution_json.sh "backup_db" "partial" 2 \ # "2026-04-01T02:00:00Z" "2026-04-01T02:00:03Z" 3000 /tmp/steps.tsv # # FORMATO steps_file (TSV sin cabecera, 6 columnas): # nameactionstatuselapsed_msoutputerror # Los campos output y error pueden estar vacios. # status valido: ok | error # # NOTA sobre IFS y tabs: bash trata tab como whitespace en IFS y colapsa # campos vacios consecutivos. Se usa 'cut -f N' para parsear cada columna # de forma correcta cuando hay campos vacios entre tabs. report_execution_json() { local flow_name="$1" local status="$2" local exit_code="$3" local started_at="$4" local ended_at="$5" local duration_ms="$6" local steps_file="$7" if [[ -z "$flow_name" || -z "$status" || -z "$exit_code" || \ -z "$started_at" || -z "$ended_at" || -z "$duration_ms" || \ -z "$steps_file" ]]; then echo "report_execution_json: uso: report_execution_json " >&2 return 1 fi if [[ ! -f "$steps_file" ]]; then echo "report_execution_json: archivo de pasos no encontrado: $steps_file" >&2 return 1 fi if command -v jq >/dev/null 2>&1; then _report_execution_json_jq \ "$flow_name" "$status" "$exit_code" \ "$started_at" "$ended_at" "$duration_ms" "$steps_file" else _report_execution_json_printf \ "$flow_name" "$status" "$exit_code" \ "$started_at" "$ended_at" "$duration_ms" "$steps_file" fi } # --- Implementacion con jq --- _report_execution_json_jq() { local flow_name="$1" status="$2" exit_code="$3" local started_at="$4" ended_at="$5" duration_ms="$6" steps_file="$7" local steps_ok=0 steps_failed=0 local steps_json="[]" while IFS= read -r line; do [[ -z "$line" ]] && continue local s_name s_action s_status s_elapsed s_output s_error s_name=$(printf '%s' "$line" | cut -f1) s_action=$(printf '%s' "$line" | cut -f2) s_status=$(printf '%s' "$line" | cut -f3) s_elapsed=$(printf '%s' "$line" | cut -f4) s_output=$(printf '%s' "$line" | cut -f5) s_error=$(printf '%s' "$line" | cut -f6) [[ -z "$s_name" ]] && continue local step_obj step_obj=$(jq -n \ --arg name "$s_name" \ --arg action "$s_action" \ --arg st "$s_status" \ --argjson ms "${s_elapsed:-0}" \ --arg output "$s_output" \ --arg error "$s_error" \ '{name: $name, action: $action, status: $st, elapsed_ms: $ms} + (if $output != "" then {output: $output} else {} end) + (if $error != "" then {error: $error} else {} end)') steps_json=$(printf '%s' "$steps_json" | jq --argjson step "$step_obj" '. + [$step]') if [[ "$s_status" == "ok" ]]; then ((steps_ok++)) else ((steps_failed++)) fi done < "$steps_file" local steps_total=$(( steps_ok + steps_failed )) jq -n \ --arg name "$flow_name" \ --arg st "$status" \ --argjson exit_code "$exit_code" \ --arg started_at "$started_at" \ --arg ended_at "$ended_at" \ --argjson duration_ms "$duration_ms" \ --argjson total "$steps_total" \ --argjson ok "$steps_ok" \ --argjson failed "$steps_failed" \ --argjson steps "$steps_json" \ '{ name: $name, status: $st, exit_code: $exit_code, started_at: $started_at, ended_at: $ended_at, duration_ms: $duration_ms, steps_total: $total, steps_ok: $ok, steps_failed: $failed, steps: $steps }' } # --- Implementacion con printf (fallback sin jq) --- _json_escape_rj() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//$'\n'/\\n}" s="${s//$'\r'/\\r}" s="${s//$'\t'/\\t}" printf '%s' "$s" } _report_execution_json_printf() { local flow_name="$1" status="$2" exit_code="$3" local started_at="$4" ended_at="$5" duration_ms="$6" steps_file="$7" local steps_ok=0 steps_failed=0 local steps_parts=() while IFS= read -r line; do [[ -z "$line" ]] && continue local s_name s_action s_status s_elapsed s_output s_error s_name=$(printf '%s' "$line" | cut -f1) s_action=$(printf '%s' "$line" | cut -f2) s_status=$(printf '%s' "$line" | cut -f3) s_elapsed=$(printf '%s' "$line" | cut -f4) s_output=$(printf '%s' "$line" | cut -f5) s_error=$(printf '%s' "$line" | cut -f6) [[ -z "$s_name" ]] && continue local part part=$(printf '{"name":"%s","action":"%s","status":"%s","elapsed_ms":%s' \ "$(_json_escape_rj "$s_name")" \ "$(_json_escape_rj "$s_action")" \ "$(_json_escape_rj "$s_status")" \ "${s_elapsed:-0}") if [[ -n "$s_output" ]]; then part+=",\"output\":\"$(_json_escape_rj "$s_output")\"" fi if [[ -n "$s_error" ]]; then part+=",\"error\":\"$(_json_escape_rj "$s_error")\"" fi part+="}" steps_parts+=("$part") if [[ "$s_status" == "ok" ]]; then ((steps_ok++)) else ((steps_failed++)) fi done < "$steps_file" local steps_total=$(( steps_ok + steps_failed )) local steps_array="[" local first=1 for part in "${steps_parts[@]}"; do if [[ $first -eq 1 ]]; then steps_array+="$part" first=0 else steps_array+=",$part" fi done steps_array+="]" printf '{"name":"%s","status":"%s","exit_code":%s,"started_at":"%s","ended_at":"%s","duration_ms":%s,"steps_total":%s,"steps_ok":%s,"steps_failed":%s,"steps":%s}\n' \ "$(_json_escape_rj "$flow_name")" \ "$(_json_escape_rj "$status")" \ "$exit_code" \ "$(_json_escape_rj "$started_at")" \ "$(_json_escape_rj "$ended_at")" \ "$duration_ms" \ "$steps_total" \ "$steps_ok" \ "$steps_failed" \ "$steps_array" } # Permitir ejecucion directa (no solo sourced) if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then report_execution_json "$@" fi