1003 lines
25 KiB
Markdown
1003 lines
25 KiB
Markdown
|
|
# 🔍 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.
|