Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
25 KiB
25 KiB
🔍 Validación y Registro con NuShell + KCL
Fecha: 2025-11-20 Clasificación: Implementación operacional Enfoque: Automatización via NuShell + Leveraging KCL native capabilities
📋 Tabla de Contenidos
- Visión General
- Validación Automática (KCL nativa)
- Scripts NuShell para Validación
- Sistema de Registro (Service Discovery)
- Integración en CI/CD
- Monitoreo y Alertas
🎯 Visión General
KCL proporciona:
├─ Validación de esquemas (tipos)
├─ Detección de ciclos de dependencias
├─ Validación de referencias de servicios
├─ Detección de conflictos de puertos
└─ Validación de recursos (CPU/Memory)
→ TODO esto "on the fly" en compilación
NuShell proporciona:
├─ Orquestación de comandos
├─ Parsing de salidas
├─ CI/CD integration
├─ Generación de reportes
└─ Alertas y notificaciones
Flujo Completo
┌──────────────────────────────────────────────┐
│ Cambio en services.k (developer comitea) │
└──────────────────────────────────────────────┘
↓ (git push)
┌──────────────────────────────────────────────┐
│ GitHub Actions / GitLab CI │
└──────────────────────────────────────────────┘
↓
validate-catalog.nu
↓
┌─────────────────────────────────────────┐
│ 1. KCL type-check │
│ └─ Detecta errores de esquema │
│ │
│ 2. Ciclos de dependencias │
│ └─ KCL políticas (policies.k) │
│ │
│ 3. Conflictos de puertos │
│ └─ Script NuShell analiza │
│ │
│ 4. Referencias válidas │
│ └─ KCL valida existencia │
│ │
│ 5. Cambios compatibles │
│ └─ Script compara con versión anterior
└─────────────────────────────────────────┘
↓
¿VÁLIDO?
↙ ↘
SÍ NO
↓ ↓
✅ ❌ Reject + Notificar
Deploy
⚡ Validación Automática (KCL nativa)
1. Qué Valida KCL Automáticamente
# El compilador de KCL RECHAZA automáticamente:
# ❌ Tipo incorrecto
service = {
id: 123 # ERROR: Esperaba str, recibió int
}
# ❌ Campo requerido faltante
service = {
id: "api"
# ERROR: name es requerido
}
# ❌ Referencia a servicio inexistente
depends_on: ["service-que-no-existe"] # ERROR en validación
# ❌ Puerto duplicado en mismo host
ports: [
{port: 3000},
{port: 3000} # ERROR: Puerto duplicado
]
# ❌ Ciclo de dependencias
service_a = { depends_on: ["service_b"] }
service_b = { depends_on: ["service_a"] } # ERROR: Ciclo detectado
2. Política de Validación: No Ciclos
# policies.k - Validación nativa
# Schema base que define estructura y tipos
Service = {
id: str
name: str
depends_on: [str]
port: int
# ... más campos
}
# Función para detectar ciclos
validate_no_dependency_cycles = lambda services {
"""
KCL aplica esta validación automáticamente
al compilar las definiciones de servicios
"""
def has_cycle(service_id, visited, rec_stack, graph):
visited[service_id] = True
rec_stack[service_id] = True
for dep in graph.get(service_id, []):
if dep not in visited:
if has_cycle(dep, visited, rec_stack, graph):
return True
elif rec_stack.get(dep):
return True
rec_stack[service_id] = False
return False
# Construir grafo de dependencias
graph = {s.id: s.depends_on for s in services}
# Validar cada servicio
for service_id in graph:
assert not has_cycle(service_id, {}, {}, graph), \
"Cyclic dependency detected in service: {}".format(service_id)
}
# Validar que referencias existen
validate_all_dependencies_exist = lambda services {
service_ids = {s.id for s in services}
for service in services:
for dep_id in service.depends_on:
assert dep_id in service_ids, \
"Service '{}' depends on non-existent service '{}'".format(
service.id, dep_id
)
}
# Validar puertos sin conflicto
validate_no_port_conflicts = lambda services {
port_to_service = {}
for service in services:
for port in service.ports:
port_num = port.port
assert port_num not in port_to_service, \
"Port {} used by both '{}' and '{}'".format(
port_num,
port_to_service[port_num],
service.id
)
port_to_service[port_num] = service.id
}
# Validar restricciones de recursos
validate_resource_constraints = lambda services {
# CPU/Memory deben cumplir formato y restricciones
for service in services:
assert is_valid_cpu_format(service.resources.cpu), \
"Invalid CPU format: {}".format(service.resources.cpu)
assert is_valid_memory_format(service.resources.memory), \
"Invalid memory format: {}".format(service.resources.memory)
}
3. Ejecución: KCL Valida en Compilación
# KCL compila y valida automáticamente
$ kcl compile services.k
# Compila exitosamente SOLO si:
# ✅ Todos los tipos son correctos
# ✅ No hay ciclos de dependencias
# ✅ Todas las referencias existen
# ✅ No hay conflictos de puertos
# ✅ Recursos tienen formato válido
# Si hay error:
$ kcl compile services.k
Error: Service 'api' depends on non-existent service 'db'
at services.k:42:15
# El error previene deployment automáticamente
🔧 Scripts NuShell para Validación
1. validate-catalog.nu - Script Principal
#!/usr/bin/env nu
# Script de validación completa del catálogo
# Uso: nu validate-catalog.nu [--check-only] [--verbose]
# Configuración
const KCL_PATH = "provisioning/services.k"
const PATTERNS_PATH = "provisioning/patterns.k"
const POLICIES_PATH = "provisioning/policies.k"
const REPORT_FILE = "validation-report.json"
# Colores
const GREEN = "[32m"
const RED = "[91m"
const YELLOW = "[33m"
const BLUE = "[34m"
const RESET = "[0m"
# Parsear argumentos
let check_only = ($env.COMMAND_LINE? | str contains "--check-only")
let verbose = ($env.COMMAND_LINE? | str contains "--verbose")
print $"($BLUE)🔍 Iniciando validación del catálogo($RESET)"
print ""
# 1. VALIDACIÓN KCL: Type-checking
print $"($BLUE)1️⃣ Validación de tipos KCL($RESET)"
try {
let kcl_output = (kcl compile $KCL_PATH 2>&1 | str trim)
if ($kcl_output =~ "Error:") {
print $"($RED)✗ Error de compilación KCL($RESET)"
print $kcl_output
exit 1
} else {
print $"($GREEN)✓ Tipos KCL válidos($RESET)"
}
} catch {
print $"($RED)✗ KCL no está instalado o error en compilación($RESET)"
exit 1
}
print ""
# 2. VALIDACIÓN: Ciclos de Dependencias
print $"($BLUE)2️⃣ Validación: Ciclos de dependencias($RESET)"
let dep_check = (nu scripts/check-dependencies.nu $KCL_PATH)
if ($dep_check.has_cycles) {
print $"($RED)✗ Ciclos detectados en dependencias($RESET)"
print $dep_check.cycles
exit 1
} else {
print $"($GREEN)✓ Sin ciclos de dependencias($RESET)"
if $verbose {
print $" Servicios validados: ($dep_check.total_services)"
print $" Dependencias únicas: ($dep_check.total_dependencies)"
}
}
print ""
# 3. VALIDACIÓN: Conflictos de Puertos
print $"($BLUE)3️⃣ Validación: Conflictos de puertos($RESET)"
let port_check = (nu scripts/check-ports.nu $KCL_PATH)
if ($port_check.has_conflicts) {
print $"($RED)✗ Conflictos de puertos detectados($RESET)"
print $port_check.conflicts
exit 1
} else {
print $"($GREEN)✓ Sin conflictos de puertos($RESET)"
if $verbose {
print $" Puertos mapeados: ($port_check.total_ports)"
let unique_ports = ($port_check.ports | length)
print $" Puertos únicos: $unique_ports"
}
}
print ""
# 4. VALIDACIÓN: Compatibilidad hacia Atrás
print $"($BLUE)4️⃣ Validación: Compatibilidad$(RESET)"
let compat_check = (nu scripts/check-compatibility.nu $KCL_PATH)
if ($compat_check.breaking_changes) {
print $"($YELLOW)⚠️ Breaking changes detectados($RESET)"
print $compat_check.changes
if not $check_only {
print $"($YELLOW)Continuar? (s/n)($RESET)"
let response = (input)
if $response != "s" {
exit 1
}
}
} else {
print $"($GREEN)✓ Compatible con versión anterior($RESET)"
}
print ""
# 5. VALIDACIÓN: Integridad de Patrones
print $"($BLUE)5️⃣ Validación: Integridad de patrones($RESET)"
let pattern_check = (nu scripts/check-patterns.nu $PATTERNS_PATH $KCL_PATH)
if ($pattern_check.invalid_patterns | length) > 0 {
print $"($RED)✗ Patrones inválidos($RESET)"
for pattern in $pattern_check.invalid_patterns {
print $" - ($pattern.id): ($pattern.error)"
}
exit 1
} else {
print $"($GREEN)✓ Todos los patrones válidos($RESET)"
if $verbose {
print $" Total de patrones: ($pattern_check.total_patterns)"
}
}
print ""
# 6. GENERACIÓN DE REPORTE
print $"($BLUE)6️⃣ Generando reporte($RESET)"
let report = {
timestamp: (date now),
status: "passed",
validations: {
kcl_types: {status: "passed", message: "Tipos KCL válidos"}
dependencies: {status: "passed", cycles: 0, total: $dep_check.total_dependencies}
ports: {status: "passed", conflicts: 0, total: $port_check.total_ports}
compatibility: {status: ($compat_check.breaking_changes | if . then "warning" else "passed" end)}
patterns: {status: "passed", total: $pattern_check.total_patterns}
}
}
$report | to json | save --force $REPORT_FILE
print $"($GREEN)✓ Reporte guardado en ($REPORT_FILE)($RESET)"
print ""
print $"($GREEN)═══════════════════════════════════════($RESET)"
print $"($GREEN)✅ Validación completada exitosamente($RESET)"
print $"($GREEN)═══════════════════════════════════════($RESET)"
print ""
# Salir con código de éxito
exit 0
2. check-dependencies.nu - Análisis de Dependencias
#!/usr/bin/env nu
# Analizar ciclos de dependencias en servicios
# Uso: nu check-dependencies.nu services.k
let kcl_file = $in
# Extraer servicios y dependencias de KCL
# Esto analiza la salida compilada de KCL
let services = (
kcl compile $kcl_file 2>&1
| grep "service"
| each { |line|
let parts = ($line | split column "=" | transpose)
{
id: ($parts.0.column1 | str trim)
depends_on: ($parts.1.column1 | str trim | split row "," | each { |d| $d | str trim })
}
}
)
# Implementar detección de ciclos (algoritmo DFS)
def has_cycle [graph, node, visited, rec_stack] {
$visited | upsert ($node | tostring) true
$rec_stack | upsert ($node | tostring) true
let neighbors = ($graph | get $node)
for neighbor in $neighbors {
if not ($visited | has ($neighbor | tostring)) {
if (has_cycle $graph $neighbor $visited $rec_stack) {
return true
}
} else if ($rec_stack | get ($neighbor | tostring)) {
return true
}
}
$rec_stack | upsert ($node | tostring) false
false
}
# Construir grafo y detectar ciclos
let graph = (
$services
| reduce .[] as $service (
{};
. + {($service.id): $service.depends_on}
)
)
let cycles = ($services | filter { |s|
has_cycle $graph $s.id {} {}
})
{
has_cycles: ($cycles | length) > 0
cycles: $cycles
total_services: ($services | length)
total_dependencies: (
$services
| map { |s| $s.depends_on | length }
| math sum
)
}
3. check-ports.nu - Análisis de Puertos
#!/usr/bin/env nu
# Detectar conflictos de puertos
# Uso: nu check-ports.nu services.k
let kcl_file = $in
# Extraer puertos de KCL compilado
let ports = (
kcl compile $kcl_file 2>&1
| grep -E "port:\s*[0-9]+"
| parse "{service}: {port}"
| each {
let port_num = ($in.port | into int)
{
service: $in.service
port: $port_num
}
}
)
# Detectar conflictos
let grouped = (
$ports
| group-by { |p| $p.port }
)
let conflicts = (
$grouped
| filter { |group| ($group | length) > 1 }
)
{
has_conflicts: ($conflicts | length) > 0
conflicts: (
$conflicts
| map { |group|
{
port: ($group.0.port)
services: ($group | map { |p| $p.service })
}
}
)
total_ports: ($ports | length)
ports: $ports
}
4. check-patterns.nu - Validación de Patrones
#!/usr/bin/env nu
# Validar que patrones referencian servicios existentes
# Uso: nu check-patterns.nu patterns.k services.k
let patterns_file = $in.0
let services_file = $in.1
# Obtener lista de servicios
let valid_services = (
kcl compile $services_file 2>&1
| grep "id:"
| parse '{id: "{service}"}'
| map { |row| $row.service }
)
# Validar cada patrón
let patterns = (
kcl compile $patterns_file 2>&1
| parse '{id: "{pattern_id}", services: [{services}]}'
)
let invalid = (
$patterns
| filter { |pattern|
let pattern_services = (
$pattern.services
| split row ","
| each { |s| $s | str trim }
)
let invalid_services = (
$pattern_services
| filter { |svc| not ($svc in $valid_services) }
)
($invalid_services | length) > 0
}
| map { |pattern|
{
id: $pattern.pattern_id
error: "Contiene referencias a servicios inexistentes"
}
}
)
{
total_patterns: ($patterns | length)
invalid_patterns: $invalid
status: (if ($invalid | length) == 0 { "passed" } else { "failed" })
}
5. check-compatibility.nu - Cambios hacia Atrás
#!/usr/bin/env nu
# Detectar breaking changes respecto a versión anterior
# Uso: nu check-compatibility.nu services.k
let kcl_file = $in
# Obtener versión actual desde git
let current = (kcl compile $kcl_file 2>&1)
# Obtener versión anterior
let previous = (git show HEAD:$kcl_file | kcl compile - 2>&1)
# Detectar cambios
let added_services = (
$current
| grep "id:"
| split row ","
| map { |s| $s | str trim }
)
let removed_services = (
$previous
| grep "id:"
| split row ","
| map { |s| $s | str trim }
| filter { |s| not ($s in $added_services) }
)
let breaking = ($removed_services | length) > 0
{
breaking_changes: $breaking
changes: {
removed_services: $removed_services
summary: if $breaking {
"WARNING: Servicios removidos (breaking change)"
} else {
"No breaking changes"
}
}
}
📦 Sistema de Registro (Service Discovery)
1. service-catalog.nu - Registro Principal
#!/usr/bin/env nu
# Gestor de catálogo de servicios
# Uso: nu service-catalog.nu [list|ports|groups|patterns]
const KCL_SERVICES_FILE = "provisioning/services.k"
const KCL_PATTERNS_FILE = "provisioning/patterns.k"
def list [] {
"""
Listar todos los servicios disponibles
"""
print "📋 Servicios disponibles:"
print ""
let services = (
kcl compile $KCL_SERVICES_FILE 2>&1
| parse '{id: "{id}", name: "{name}", image: "{image}"}'
)
$services
| each { |s|
print $" • ($s.id) - ($s.name)"
print $" imagen: ($s.image)"
}
}
def ports [] {
"""
Mostrar mapeo de puertos
"""
print "🔌 Mapeo de puertos:"
print ""
let ports = (
kcl compile $KCL_SERVICES_FILE 2>&1
| parse '{service: "{service}", port: {port}}'
)
let sorted = ($ports | sort-by port)
$sorted
| each { |p|
print $" ($p.port) → ($p.service)"
}
}
def groups [] {
"""
Listar grupos de servicios
"""
print "📦 Grupos de servicios:"
print ""
let groups = (
kcl compile $KCL_SERVICES_FILE 2>&1
| grep "group:"
| parse '{group: "{group}", services: [{services}]}'
)
$groups
| each { |g|
print $" ($g.group):"
print $" servicios: ($g.services | str trim)"
}
}
def patterns [] {
"""
Listar patrones de despliegue disponibles
"""
print "🎯 Patrones de despliegue:"
print ""
let patterns = (
kcl compile $KCL_PATTERNS_FILE 2>&1
| parse '{id: "{id}", name: "{name}", environment: "{environment}"}'
)
$patterns
| each { |p|
print $" ($p.id) - ($p.name) [($p.environment)]"
}
}
def search [query: string] {
"""
Buscar servicios por nombre o tag
"""
print $"🔍 Buscando: ($query)"
print ""
let services = (
kcl compile $KCL_SERVICES_FILE 2>&1
| parse '{id: "{id}", name: "{name}", tags: [{tags}]}'
| filter { |s|
($s.id | str contains $query) or
($s.name | str contains $query) or
($s.tags | str contains $query)
}
)
if ($services | length) == 0 {
print " ❌ No se encontraron servicios"
} else {
$services | each { |s|
print $" • ($s.id)"
}
}
}
def dependencies [service: string] {
"""
Mostrar dependencias de un servicio
"""
print $"📍 Dependencias de ($service):"
print ""
let deps = (
kcl compile $KCL_SERVICES_FILE 2>&1
| parse '{id: "{id}", depends_on: [{depends}]}'
| filter { |s| $s.id == $service }
)
if ($deps | length) == 0 {
print " ❌ Servicio no encontrado"
} else {
let depends = ($deps.0.depends | split row "," | each { |d| $d | str trim })
if ($depends | length) == 0 {
print " ✓ Sin dependencias"
} else {
$depends | each { |d| print $" ← ($d)" }
}
}
}
# Parsear argumentos
let action = ($nu.env.ARGS? | get 0 | default "list")
match $action {
"list" => { list }
"ports" => { ports }
"groups" => { groups }
"patterns" => { patterns }
"search" => { search ($nu.env.ARGS?.1 | default "") }
"dependencies" => { dependencies ($nu.env.ARGS?.1 | default "") }
_ => {
print "Uso: nu service-catalog.nu [list|ports|groups|patterns|search|dependencies]"
}
}
2. Auto-Discovery: Registro Automático
#!/usr/bin/env nu
# Auto-discover servicios y registrarlos en cache
# Uso: nu discover-services.nu
const CACHE_FILE = ".syntaxis/service-registry-cache.json"
print "🔍 Auto-discovering servicios..."
# 1. Compilar KCL
let kcl_services = (
kcl compile provisioning/services.k 2>&1
)
# 2. Extraer información
let services = (
$kcl_services
| grep "service:"
| parse '{id: "{id}", port: {port}}'
)
# 3. Enrichir con información adicional
let enriched = (
$services
| each { |service|
let details = (
$kcl_services
| grep $service.id
| parse '{status: "active"}'
)
{
id: $service.id
port: $service.port
status: "active"
discovered_at: (date now)
}
}
)
# 4. Guardar en cache
mkdir -p (.syntaxis)
$enriched | to json | save --force $CACHE_FILE
print $"✅ ($enriched | length) servicios registrados"
print $"📁 Cache guardado en ($CACHE_FILE)"
🔄 Integración en CI/CD
1. GitHub Actions Workflow
# .github/workflows/validate-catalog.yml
name: Validate Service Catalog
on:
push:
paths:
- 'provisioning/services.k'
- 'provisioning/patterns.k'
- 'provisioning/policies.k'
pull_request:
paths:
- 'provisioning/services.k'
- 'provisioning/patterns.k'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install NuShell
run: cargo install nushell
- name: Install KCL
run: |
wget https://github.com/kcl-lang/kcl/releases/download/v0.8.0/kcl-v0.8.0-linux-x86_64.tar.gz
tar xzf kcl-v0.8.0-linux-x86_64.tar.gz
sudo mv kcl-v0.8.0/bin/kcl /usr/local/bin/
- name: Run validation
run: nu validate-catalog.nu --verbose
- name: Upload report
if: always()
uses: actions/upload-artifact@v3
with:
name: validation-report
path: validation-report.json
- name: Comment on PR
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ Validación de catálogo fallida. Ver detalles en los artifacts.'
})
2. Pre-Commit Hook
# .git/hooks/pre-commit
#!/bin/bash
echo "🔍 Validando catálogo antes de commit..."
if nu validate-catalog.nu --check-only; then
echo "✅ Catálogo válido"
exit 0
else
echo "❌ Validación fallida. Resuelve los errores antes de commitear."
exit 1
fi
📊 Monitoreo y Alertas
1. monitor-catalog.nu - Monitoreo Continuo
#!/usr/bin/env nu
# Monitorear cambios en el catálogo
# Uso: nu monitor-catalog.nu
const WATCH_FILE = "provisioning/services.k"
const CHECK_INTERVAL = 5 # segundos
print "👁️ Monitoreando catálogo de servicios..."
print ""
let last_hash = (
open $WATCH_FILE
| hash sha256
)
loop {
let current_hash = (
open $WATCH_FILE
| hash sha256
)
if $current_hash != $last_hash {
print ""
print "📝 Cambio detectado en ($WATCH_FILE)"
print $"⏰ (date now | format date '%H:%M:%S')"
# Ejecutar validación
let validation = (nu validate-catalog.nu 2>&1)
if ($validation | str contains "✅") {
print "✅ Validación pasada"
} else {
print "❌ Validación fallida"
# Enviar alerta
# slack-notify "Validación de catálogo fallida"
}
# Actualizar hash
let last_hash = $current_hash
}
sleep 5sec
}
2. Alertas por Slack
#!/usr/bin/env nu
# Enviar alerta a Slack
# Uso: nu alert.nu "mensaje"
def slack_notify [message: string] {
let webhook_url = $env.SLACK_WEBHOOK_URL?
if ($webhook_url | is-empty) {
print "⚠️ SLACK_WEBHOOK_URL no configurado"
return
}
let payload = {
text: "⚠️ Catalog Validation Alert"
blocks: [
{
type: "section"
text: {
type: "mrkdwn"
text: $message
}
}
]
}
http post $webhook_url ($payload | to json)
print "✅ Alerta enviada a Slack"
}
# Uso
let message = "❌ Validación fallida\nVerifica el reporte: https://..."
slack_notify $message
🎯 Resumen: KCL vs NuShell
| Responsabilidad | KCL | NuShell |
|---|---|---|
| Validación de tipos | ✅ Automática en compilación | - |
| Ciclos de dependencias | ✅ Automática en compilación | ✅ Verificación adicional |
| Conflictos de puertos | ✅ Esquema KCL | ✅ Análisis detallado |
| Referencias válidas | ✅ Type system | ✅ Cross-check |
| Registro/Discovery | - | ✅ Auto-discovery + cache |
| Orquestación | - | ✅ Scripts y workflows |
| CI/CD Integration | - | ✅ Pipelines GitHub/GitLab |
| Reportes | - | ✅ JSON + alertas |
📋 Checklist de Implementación
- Crear
policies.kcon validaciones - Crear
validate-catalog.nu - Crear
check-dependencies.nu - Crear
check-ports.nu - Crear
check-patterns.nu - Crear
service-catalog.nu(registro) - Crear
.git/hooks/pre-commit - Crear
.github/workflows/validate-catalog.yml - Crear
monitor-catalog.nu - Setup alertas Slack
- Documentar CLI commands para DevOps
- Test con cambios (breaking y non-breaking)
Conclusión: KCL proporciona validación automática "on the fly" en compilación. NuShell orquesta la validación, gestiona el registro, e integra con CI/CD. Juntos proporcionan un sistema robusto sin necesidad de recodificar lógica de validación en Rust.