# 🔍 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 1. [Visión General](#visión-general) 2. [Validación Automática (KCL nativa)](#validación-automática-kcl-nativa) 3. [Scripts NuShell para Validación](#scripts-nushell-para-validación) 4. [Sistema de Registro (Service Discovery)](#sistema-de-registro-service-discovery) 5. [Integración en CI/CD](#integración-en-cicd) 6. [Monitoreo y Alertas](#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 ```kcl # 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 ```kcl # 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 ```bash # 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 ```nushell #!/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 ```nushell #!/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 ```nushell #!/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 ```nushell #!/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 ```nushell #!/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 ```nushell #!/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 ```nushell #!/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 ```yaml # .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 ```bash # .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 ```nushell #!/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 ```nushell #!/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.k` con 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.