774 lines
19 KiB
Markdown
774 lines
19 KiB
Markdown
|
|

|
||
|
|
|
||
|
|
# ✅ ARQUITECTURA CORRECTA - Declaración KCL Modular en Syntaxis
|
||
|
|
|
||
|
|
**Fecha**: 2025-11-20
|
||
|
|
**Estado**: Validado y correcto
|
||
|
|
**Patrón**: Declaración local, consumo adaptativo
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 La Solución Correcta en Una Página
|
||
|
|
|
||
|
|
```
|
||
|
|
syntaxis/configs/*.k (MODULAR KCL)
|
||
|
|
↓ ÚNICA fuente de verdad
|
||
|
|
├─ deployment.k (principal)
|
||
|
|
├─ services.k (módulo)
|
||
|
|
├─ presets.k (módulo)
|
||
|
|
├─ schemas.k (tipos + validación)
|
||
|
|
└─ defaults.k (valores + constraints)
|
||
|
|
|
||
|
|
NO hay generación almacenada:
|
||
|
|
✅ kcl export on-demand (stdout → temporal)
|
||
|
|
✅ Tera templates para adaptación
|
||
|
|
✅ Consumo adaptativo (provisioning/provctl/installer)
|
||
|
|
|
||
|
|
Validación:
|
||
|
|
✅ Nativa en KCL (compile-time)
|
||
|
|
✅ Schemas con constraints
|
||
|
|
✅ Defaults inteligentes
|
||
|
|
✅ Herencia de presets
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📐 Estructura de Archivos KCL Modular
|
||
|
|
|
||
|
|
### Ubicación
|
||
|
|
|
||
|
|
```
|
||
|
|
syntaxis/
|
||
|
|
├── Cargo.toml
|
||
|
|
├── src/
|
||
|
|
├── configs/
|
||
|
|
│ ├── deployment.k ← MAIN: Define todo
|
||
|
|
│ ├── services.k ← MODULO: Definiciones de servicios
|
||
|
|
│ ├── presets.k ← MODULO: Presets (local/dev/staging/prod)
|
||
|
|
│ ├── schemas.k ← MODULO: Types y validación
|
||
|
|
│ └── defaults.k ← MODULO: Valores por defecto + constraints
|
||
|
|
├── templates/
|
||
|
|
│ ├── provisioning-config.j2 ← Para provisioning
|
||
|
|
│ ├── provctl-config.j2 ← Para provctl
|
||
|
|
│ └── installer-settings.j2 ← Para installer interactivo
|
||
|
|
└── scripts/
|
||
|
|
└── export-config.nu ← NuShell: export KCL → YAML/JSON
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Archivo 1: schemas.k (Tipos y Validación)
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# syntaxis/configs/schemas.k
|
||
|
|
# Definiciones de tipos y esquemas con validación nativa
|
||
|
|
|
||
|
|
import regex
|
||
|
|
|
||
|
|
# Service Schema
|
||
|
|
schema Service:
|
||
|
|
"""Define un servicio de syntaxis"""
|
||
|
|
name: str
|
||
|
|
display_name: str
|
||
|
|
description: str
|
||
|
|
type: "cli" | "tui" | "server" | "web" | "database" | "messaging"
|
||
|
|
|
||
|
|
# Validación: port requerido si type es service
|
||
|
|
port?: int
|
||
|
|
check: port is not None if type in ["server", "web", "database", "messaging"], "Service con puerto requiere port"
|
||
|
|
check: 1 <= port <= 65535 if port is not None, "Port debe estar entre 1-65535"
|
||
|
|
|
||
|
|
enabled: bool = False
|
||
|
|
requires: [str] = []
|
||
|
|
optional: [str] = []
|
||
|
|
|
||
|
|
# Configuración operativa
|
||
|
|
restart_policy: "always" | "on-failure" | "never" = "on-failure"
|
||
|
|
restart_delay_seconds: int = 5
|
||
|
|
|
||
|
|
# Binario o ejecutable
|
||
|
|
binary?: {
|
||
|
|
name: str
|
||
|
|
installed_to: str
|
||
|
|
}
|
||
|
|
|
||
|
|
# Health check
|
||
|
|
health_check?: {
|
||
|
|
type: "http" | "tcp" | "script"
|
||
|
|
endpoint?: str
|
||
|
|
method?: str = "GET"
|
||
|
|
expected_status?: int = 200
|
||
|
|
interval_seconds: int = 10
|
||
|
|
timeout_seconds: int = 5
|
||
|
|
}
|
||
|
|
|
||
|
|
# Metadata operativa
|
||
|
|
platform_support: [str] = ["linux", "macos", "windows"]
|
||
|
|
min_disk_space_mb: int = 50
|
||
|
|
min_memory_mb: int = 64
|
||
|
|
background_service: bool = False
|
||
|
|
|
||
|
|
# Configuración
|
||
|
|
config_location?: str
|
||
|
|
database_required: bool = False
|
||
|
|
database_types: [str] = ["sqlite", "surrealdb"]
|
||
|
|
|
||
|
|
# Database Schema
|
||
|
|
schema Database:
|
||
|
|
"""Configuración de base de datos"""
|
||
|
|
type: "sqlite" | "postgres" | "mysql" | "surrealdb"
|
||
|
|
|
||
|
|
# Validación por tipo
|
||
|
|
path?: str
|
||
|
|
check: path is not None if type == "sqlite", "SQLite requiere path"
|
||
|
|
|
||
|
|
host?: str = "localhost"
|
||
|
|
check: host is not None if type != "sqlite", "BD remota requiere host"
|
||
|
|
|
||
|
|
port?: int
|
||
|
|
user?: str = "syntaxis"
|
||
|
|
password?: str
|
||
|
|
|
||
|
|
# Cache Schema
|
||
|
|
schema Cache:
|
||
|
|
"""Configuración de caché"""
|
||
|
|
enabled: bool = False
|
||
|
|
type: "redis" | "memcached"
|
||
|
|
host: str = "localhost"
|
||
|
|
port: int = 6379
|
||
|
|
|
||
|
|
# Preset Schema
|
||
|
|
schema Preset:
|
||
|
|
"""Define un preset de deployment"""
|
||
|
|
name: str
|
||
|
|
description: str
|
||
|
|
services_enabled: [str]
|
||
|
|
|
||
|
|
# Overrides específicos del preset
|
||
|
|
database_type?: "sqlite" | "postgres" | "mysql" | "surrealdb"
|
||
|
|
cache_enabled?: bool
|
||
|
|
|
||
|
|
# Manager para este preset
|
||
|
|
manager: "manual" | "provctl" | "provisioning" = "manual"
|
||
|
|
|
||
|
|
# Full Deployment Schema
|
||
|
|
schema Deployment:
|
||
|
|
"""Configuración completa de deployment"""
|
||
|
|
project: str = "syntaxis"
|
||
|
|
version: str
|
||
|
|
|
||
|
|
services: {str: Service}
|
||
|
|
presets: {str: Preset}
|
||
|
|
|
||
|
|
default_database: Database
|
||
|
|
default_cache: Cache
|
||
|
|
|
||
|
|
# Validación: presets deben referenciar servicios válidos
|
||
|
|
check: [
|
||
|
|
s in services.keys()
|
||
|
|
for p in presets.values()
|
||
|
|
for s in p.services_enabled
|
||
|
|
] == p.services_enabled, "Preset referencia servicio inexistente"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Archivo 2: defaults.k (Valores y Constraints)
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# syntaxis/configs/defaults.k
|
||
|
|
# Valores por defecto y constraints reutilizables
|
||
|
|
|
||
|
|
import .schemas
|
||
|
|
|
||
|
|
# Servicios por defecto
|
||
|
|
default_services: {str: schemas.Service} = {
|
||
|
|
cli = schemas.Service {
|
||
|
|
name = "syntaxis-cli"
|
||
|
|
type = "binary"
|
||
|
|
enabled = True
|
||
|
|
}
|
||
|
|
|
||
|
|
api = schemas.Service {
|
||
|
|
name = "syntaxis-api"
|
||
|
|
type = "service"
|
||
|
|
port = 3000
|
||
|
|
enabled = False
|
||
|
|
requires = ["database"]
|
||
|
|
health_check = {
|
||
|
|
type = "http"
|
||
|
|
endpoint = "http://127.0.0.1:3000/health"
|
||
|
|
interval_seconds = 10
|
||
|
|
timeout_seconds = 5
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
tui = schemas.Service {
|
||
|
|
name = "syntaxis-tui"
|
||
|
|
type = "binary"
|
||
|
|
enabled = False
|
||
|
|
}
|
||
|
|
|
||
|
|
dashboard = schemas.Service {
|
||
|
|
name = "syntaxis-dashboard"
|
||
|
|
type = "service"
|
||
|
|
port = 8080
|
||
|
|
enabled = False
|
||
|
|
requires = ["api"]
|
||
|
|
health_check = {
|
||
|
|
type = "http"
|
||
|
|
endpoint = "http://127.0.0.1:8080"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
database = schemas.Service {
|
||
|
|
name = "surrealdb"
|
||
|
|
type = "database"
|
||
|
|
port = 8000
|
||
|
|
enabled = False
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Bases de datos por defecto
|
||
|
|
default_databases: {str: schemas.Database} = {
|
||
|
|
sqlite = schemas.Database {
|
||
|
|
type = "sqlite"
|
||
|
|
path = "/var/lib/syntaxis/db.sqlite"
|
||
|
|
}
|
||
|
|
|
||
|
|
postgres = schemas.Database {
|
||
|
|
type = "postgres"
|
||
|
|
host = "localhost"
|
||
|
|
port = 5432
|
||
|
|
user = "syntaxis"
|
||
|
|
}
|
||
|
|
|
||
|
|
surrealdb = schemas.Database {
|
||
|
|
type = "surrealdb"
|
||
|
|
host = "localhost"
|
||
|
|
port = 8000
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Caché por defecto
|
||
|
|
default_cache: schemas.Cache = schemas.Cache {
|
||
|
|
enabled = False
|
||
|
|
type = "redis"
|
||
|
|
host = "localhost"
|
||
|
|
port = 6379
|
||
|
|
}
|
||
|
|
|
||
|
|
# Presets por defecto (sin overrides específicos aún)
|
||
|
|
base_presets: {str: schemas.Preset} = {
|
||
|
|
local = schemas.Preset {
|
||
|
|
name = "local"
|
||
|
|
description = "Single machine, CLI only (manual execution)"
|
||
|
|
services_enabled = ["cli"]
|
||
|
|
manager = "manual"
|
||
|
|
}
|
||
|
|
|
||
|
|
dev = schemas.Preset {
|
||
|
|
name = "dev"
|
||
|
|
description = "Full development stack with provctl orchestration"
|
||
|
|
services_enabled = ["cli", "api", "tui", "dashboard", "database"]
|
||
|
|
manager = "provctl"
|
||
|
|
database_type = "sqlite"
|
||
|
|
cache_enabled = False
|
||
|
|
}
|
||
|
|
|
||
|
|
staging = schemas.Preset {
|
||
|
|
name = "staging"
|
||
|
|
description = "Staging environment on multiple machines"
|
||
|
|
services_enabled = ["cli", "api", "dashboard", "database"]
|
||
|
|
manager = "provisioning"
|
||
|
|
database_type = "postgres"
|
||
|
|
cache_enabled = True
|
||
|
|
}
|
||
|
|
|
||
|
|
production = schemas.Preset {
|
||
|
|
name = "production"
|
||
|
|
description = "Production environment with high availability"
|
||
|
|
services_enabled = ["api", "dashboard", "database"]
|
||
|
|
manager = "provisioning"
|
||
|
|
database_type = "postgres"
|
||
|
|
cache_enabled = True
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Archivo 3: services.k (Definiciones de Servicios)
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# syntaxis/configs/services.k
|
||
|
|
# Definiciones de servicios específicas (puede refinarse)
|
||
|
|
|
||
|
|
import .schemas
|
||
|
|
import .defaults
|
||
|
|
|
||
|
|
# Servicios con defaults aplicados
|
||
|
|
services: {str: schemas.Service} = default_services | {
|
||
|
|
# Puede personalizar servicios aquí si es necesario
|
||
|
|
# Por ahora usa los defaults
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Archivo 4: presets.k (Presets Parametrizables)
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# syntaxis/configs/presets.k
|
||
|
|
# Definiciones de presets con posibilidad de override
|
||
|
|
|
||
|
|
import .schemas
|
||
|
|
import .defaults
|
||
|
|
import .services
|
||
|
|
|
||
|
|
# Presets con defaults y capacidad de personalización
|
||
|
|
presets: {str: schemas.Preset} = {
|
||
|
|
local = defaults.base_presets.local
|
||
|
|
|
||
|
|
dev = defaults.base_presets.dev {
|
||
|
|
# Puede refinarse aquí si necesita overrides específicos
|
||
|
|
# Por defecto usa los defaults
|
||
|
|
}
|
||
|
|
|
||
|
|
staging = defaults.base_presets.staging {
|
||
|
|
# Overrides específicos si es necesario
|
||
|
|
}
|
||
|
|
|
||
|
|
production = defaults.base_presets.production {
|
||
|
|
# Overrides específicos si es necesario
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Función auxiliar para obtener preset con parámetros
|
||
|
|
[params]
|
||
|
|
def get_preset(name: str, overrides: {str: str}) -> schemas.Preset:
|
||
|
|
"""Obtiene un preset y aplica overrides dinamámicos"""
|
||
|
|
preset = presets[name]
|
||
|
|
|
||
|
|
# Aplicar overrides si existen
|
||
|
|
if "database_type" in overrides:
|
||
|
|
preset.database_type = overrides["database_type"]
|
||
|
|
if "cache_enabled" in overrides:
|
||
|
|
preset.cache_enabled = overrides["cache_enabled"] == "true"
|
||
|
|
|
||
|
|
preset
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Archivo 5: deployment.k (Declaración Principal)
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# syntaxis/configs/deployment.k
|
||
|
|
# Declaración principal que integra todo
|
||
|
|
|
||
|
|
import .schemas
|
||
|
|
import .services
|
||
|
|
import .presets
|
||
|
|
import .defaults
|
||
|
|
|
||
|
|
# Deployment principal
|
||
|
|
deployment: schemas.Deployment = schemas.Deployment {
|
||
|
|
project = "syntaxis"
|
||
|
|
version = "1.0.0"
|
||
|
|
|
||
|
|
services = services
|
||
|
|
presets = presets
|
||
|
|
|
||
|
|
default_database = defaults.default_databases.sqlite
|
||
|
|
default_cache = defaults.default_cache
|
||
|
|
}
|
||
|
|
|
||
|
|
# Export principales para consumidores
|
||
|
|
export {
|
||
|
|
deployment
|
||
|
|
services
|
||
|
|
presets
|
||
|
|
default_databases
|
||
|
|
default_cache
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📜 Templates Tera para Adaptación
|
||
|
|
|
||
|
|
### Template 1: provisioning-config.j2
|
||
|
|
|
||
|
|
```jinja2
|
||
|
|
{# syntaxis/templates/provisioning-config.j2 #}
|
||
|
|
{# Genera configuración para provisioning desde KCL #}
|
||
|
|
|
||
|
|
# Configuración Syntaxis para provisioning
|
||
|
|
# Generado desde syntaxis/configs/deployment.k
|
||
|
|
|
||
|
|
project:
|
||
|
|
name = "{{ deployment.project }}"
|
||
|
|
version = "{{ deployment.version }}"
|
||
|
|
|
||
|
|
services:
|
||
|
|
{% for name, service in deployment.services %}
|
||
|
|
{% if service.enabled %}
|
||
|
|
"{{ name }}":
|
||
|
|
type = "{{ service.type }}"
|
||
|
|
{% if service.port %}
|
||
|
|
port = {{ service.port }}
|
||
|
|
{% endif %}
|
||
|
|
restart_policy = "{{ service.restart_policy }}"
|
||
|
|
{% if service.health_check %}
|
||
|
|
health_check = {
|
||
|
|
type = "{{ service.health_check.type }}"
|
||
|
|
{% if service.health_check.endpoint %}
|
||
|
|
endpoint = "{{ service.health_check.endpoint }}"
|
||
|
|
{% endif %}
|
||
|
|
interval = {{ service.health_check.interval_seconds }}
|
||
|
|
}
|
||
|
|
{% endif %}
|
||
|
|
{% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
|
||
|
|
database:
|
||
|
|
type = "{{ deployment.default_database.type }}"
|
||
|
|
{% if deployment.default_database.host %}
|
||
|
|
host = "{{ deployment.default_database.host }}"
|
||
|
|
{% endif %}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Template 2: provctl-config.j2
|
||
|
|
|
||
|
|
```jinja2
|
||
|
|
{# syntaxis/templates/provctl-config.j2 #}
|
||
|
|
{# Genera TOML para provctl #}
|
||
|
|
|
||
|
|
# Configuración Syntaxis para provctl
|
||
|
|
# Generado desde syntaxis/configs/deployment.k
|
||
|
|
|
||
|
|
{% for name, service in deployment.services %}
|
||
|
|
{% if service.enabled %}
|
||
|
|
[[services]]
|
||
|
|
name = "{{ service.name }}"
|
||
|
|
type = "{{ service.type }}"
|
||
|
|
{% if service.port %}
|
||
|
|
port = {{ service.port }}
|
||
|
|
{% endif %}
|
||
|
|
restart_policy = "{{ service.restart_policy }}"
|
||
|
|
|
||
|
|
{% if service.health_check %}
|
||
|
|
[services.health_check]
|
||
|
|
type = "{{ service.health_check.type }}"
|
||
|
|
{% if service.health_check.endpoint %}
|
||
|
|
endpoint = "{{ service.health_check.endpoint }}"
|
||
|
|
{% endif %}
|
||
|
|
interval_seconds = {{ service.health_check.interval_seconds }}
|
||
|
|
timeout_seconds = {{ service.health_check.timeout_seconds }}
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
{% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Template 3: installer-settings.j2
|
||
|
|
|
||
|
|
```jinja2
|
||
|
|
{# syntaxis/templates/installer-settings.j2 #}
|
||
|
|
{# Genera configuración específica para installer interactivo #}
|
||
|
|
|
||
|
|
{
|
||
|
|
"preset": "{{ preset }}",
|
||
|
|
"description": "{{ preset_description }}",
|
||
|
|
"services": [
|
||
|
|
{% for service in services_enabled %}
|
||
|
|
"{{ service }}"{{ "," if not loop.last else "" }}
|
||
|
|
{% endfor %}
|
||
|
|
],
|
||
|
|
"database": {
|
||
|
|
"type": "{{ database_type }}"
|
||
|
|
{% if database_type == "postgres" %}
|
||
|
|
,"host": "{{ database_host | default("localhost") }}",
|
||
|
|
"port": {{ database_port | default(5432) }},
|
||
|
|
"user": "{{ database_user | default("syntaxis") }}"
|
||
|
|
{% elif database_type == "sqlite" %}
|
||
|
|
,"path": "{{ database_path | default("/var/lib/syntaxis/db.sqlite") }}"
|
||
|
|
{% endif %}
|
||
|
|
},
|
||
|
|
"cache_enabled": {{ cache_enabled | lower }}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 Scripts NuShell para Exportación On-Demand
|
||
|
|
|
||
|
|
### Script: export-config.nu
|
||
|
|
|
||
|
|
```nushell
|
||
|
|
# syntaxis/scripts/export-config.nu
|
||
|
|
# Exporta KCL a YAML/JSON on-demand (sin guardar)
|
||
|
|
|
||
|
|
def export-deployment [format: string = "yaml"] -> string {
|
||
|
|
"""
|
||
|
|
Exporta deployment.k a YAML o JSON
|
||
|
|
|
||
|
|
Ejemplos:
|
||
|
|
export-deployment # YAML por defecto
|
||
|
|
export-deployment --format json # Exporta como JSON
|
||
|
|
"""
|
||
|
|
|
||
|
|
# 1. Llamar kcl compile para obtener resultado
|
||
|
|
let kcl_output = (
|
||
|
|
kcl run syntaxis/configs/deployment.k
|
||
|
|
--format json
|
||
|
|
| from json
|
||
|
|
)
|
||
|
|
|
||
|
|
# 2. Convertir según formato requerido
|
||
|
|
match $format {
|
||
|
|
"json" => {
|
||
|
|
$kcl_output | to json
|
||
|
|
}
|
||
|
|
"yaml" => {
|
||
|
|
$kcl_output | to json | from json | to yaml
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
error "Formato no soportado: $format (usa 'json' o 'yaml')"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def get-preset [preset: string] -> string {
|
||
|
|
"""
|
||
|
|
Obtiene un preset específico en formato YAML
|
||
|
|
|
||
|
|
Ejemplo: get-preset dev
|
||
|
|
"""
|
||
|
|
|
||
|
|
let deployment = (export-deployment yaml | from yaml)
|
||
|
|
|
||
|
|
if ($preset not-in $deployment.presets) {
|
||
|
|
error "Preset '$preset' no existe"
|
||
|
|
}
|
||
|
|
|
||
|
|
$deployment.presets | get $preset | to yaml
|
||
|
|
}
|
||
|
|
|
||
|
|
def get-services [preset: string] -> list {
|
||
|
|
"""
|
||
|
|
Lista servicios habilitados en un preset
|
||
|
|
|
||
|
|
Ejemplo: get-services dev
|
||
|
|
"""
|
||
|
|
|
||
|
|
let deployment = (export-deployment yaml | from yaml)
|
||
|
|
$deployment.presets | get $preset | get services_enabled
|
||
|
|
}
|
||
|
|
|
||
|
|
# Comandos disponibles
|
||
|
|
export def "config export" [format: string = "yaml"] {
|
||
|
|
export-deployment $format
|
||
|
|
}
|
||
|
|
|
||
|
|
export def "config preset" [preset: string] {
|
||
|
|
get-preset $preset
|
||
|
|
}
|
||
|
|
|
||
|
|
export def "config services" [preset: string] {
|
||
|
|
get-services $preset
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Flujo: Cómo Se Consume
|
||
|
|
|
||
|
|
### Escenario 1: provisioning (si hay workspace)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# provisioning/kcl/syntaxis-integration.k
|
||
|
|
import /path/to/syntaxis/configs/deployment as sx
|
||
|
|
|
||
|
|
# Leer declaración KCL de syntaxis
|
||
|
|
syntaxis_config = sx.deployment
|
||
|
|
|
||
|
|
# Validar contra esquemas provisioning
|
||
|
|
# (provisioning puede validar porque deployment.k es KCL)
|
||
|
|
|
||
|
|
# Generar configuración provisioning-específica usando template
|
||
|
|
provisioning_settings = render_template(
|
||
|
|
"syntaxis/templates/provisioning-config.j2",
|
||
|
|
{ deployment = syntaxis_config }
|
||
|
|
)
|
||
|
|
|
||
|
|
# Resultado: config adaptada a provisioning
|
||
|
|
```
|
||
|
|
|
||
|
|
### Escenario 2: provctl (local con NuShell)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Developer ejecuta:
|
||
|
|
syntaxis-installer --preset dev
|
||
|
|
|
||
|
|
# El installer:
|
||
|
|
# 1. Lee syntaxis/configs/deployment.k
|
||
|
|
# 2. Ofrece: "¿Quieres development automático con provctl?"
|
||
|
|
# 3. Si SÍ:
|
||
|
|
# - Ejecuta: nu scripts/export-config.nu yaml
|
||
|
|
# - Obtiene: YAML desde KCL (temporal, stdout)
|
||
|
|
# - Pasa a Tera: installer-settings.j2
|
||
|
|
# - Pregunta: "¿Qué BD? [sqlite/postgres]"
|
||
|
|
# - Genera: YAML específico para provctl (temporal, no guardado)
|
||
|
|
# - Ejecuta: provctl config apply <yaml>
|
||
|
|
# 4. Servicios se inician automáticamente
|
||
|
|
```
|
||
|
|
|
||
|
|
### Escenario 3: syntaxis-installer (interactivo)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
$ syntaxis-installer --preset dev
|
||
|
|
|
||
|
|
¿Cómo deseas instalar Syntaxis?
|
||
|
|
1. local - CLI solamente (manual)
|
||
|
|
2. dev - Full stack con provctl (recomendado)
|
||
|
|
3. staging - Multi-máquina con provisioning
|
||
|
|
4. production - HA con provisioning
|
||
|
|
|
||
|
|
> Selecciona [1-4]: 2
|
||
|
|
|
||
|
|
Preset 'dev' seleccionado.
|
||
|
|
¿Qué base de datos prefieres?
|
||
|
|
1. sqlite - Archivo local (rápido, desarrollo)
|
||
|
|
2. postgres - BD remota (escalable)
|
||
|
|
3. surrealdb - Multi-backend (moderno)
|
||
|
|
|
||
|
|
> Selecciona [1-3]: 1
|
||
|
|
|
||
|
|
¿Habilitar caché (Redis)?
|
||
|
|
[S/n]: n
|
||
|
|
|
||
|
|
Verificando dependencias...
|
||
|
|
✅ provctl disponible
|
||
|
|
✅ Git disponible
|
||
|
|
|
||
|
|
Generando configuración...
|
||
|
|
$ nu scripts/export-config.nu yaml > /tmp/syntaxis-dev.yaml
|
||
|
|
$ tera --template syntaxis/templates/installer-settings.j2 \
|
||
|
|
--input /tmp/syntaxis-dev.yaml \
|
||
|
|
--values preset=dev,database_type=sqlite \
|
||
|
|
> /tmp/syntaxis-final.yaml
|
||
|
|
|
||
|
|
Aplicando configuración...
|
||
|
|
$ provctl config apply /tmp/syntaxis-final.yaml
|
||
|
|
|
||
|
|
Iniciando servicios...
|
||
|
|
✅ syntaxis-cli iniciado
|
||
|
|
✅ syntaxis-api iniciado (http://127.0.0.1:3000)
|
||
|
|
✅ syntaxis-dashboard iniciado (http://127.0.0.1:8080)
|
||
|
|
|
||
|
|
¡Listo! Syntaxis está corriendo en modo dev.
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Validación en KCL (Nativa, Compile-Time)
|
||
|
|
|
||
|
|
### Ejemplos de Validación
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
# En schemas.k
|
||
|
|
|
||
|
|
schema Service:
|
||
|
|
name: str
|
||
|
|
type: "binary" | "service" | "database"
|
||
|
|
port?: int
|
||
|
|
|
||
|
|
# ✅ VALIDACIÓN 1: port requerido si service
|
||
|
|
check: port is not None if type == "service",
|
||
|
|
"Service debe tener port"
|
||
|
|
|
||
|
|
# ✅ VALIDACIÓN 2: rango válido
|
||
|
|
check: 1 <= port <= 65535 if port is not None,
|
||
|
|
"Port debe estar entre 1 y 65535"
|
||
|
|
|
||
|
|
schema Database:
|
||
|
|
type: "sqlite" | "postgres" | "mysql"
|
||
|
|
path?: str
|
||
|
|
|
||
|
|
# ✅ VALIDACIÓN 3: path requerido para sqlite
|
||
|
|
check: path is not None if type == "sqlite",
|
||
|
|
"SQLite requiere path"
|
||
|
|
|
||
|
|
# ✅ VALIDACIÓN 4: path válido
|
||
|
|
check: regex.match(path, "^/[a-zA-Z0-9/_.-]+$") if path is not None,
|
||
|
|
"Path debe ser ruta absoluta válida"
|
||
|
|
|
||
|
|
schema Deployment:
|
||
|
|
services: {str: Service}
|
||
|
|
presets: {str: Preset}
|
||
|
|
|
||
|
|
# ✅ VALIDACIÓN 5: presets referencian servicios válidos
|
||
|
|
check: all(
|
||
|
|
service_name in services.keys()
|
||
|
|
for preset in presets.values()
|
||
|
|
for service_name in preset.services_enabled
|
||
|
|
), "Preset referencia servicio inexistente"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Resultado**: Validación at compile-time, no en runtime TOML.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Ventajas de Esta Arquitectura
|
||
|
|
|
||
|
|
| Aspecto | Ventaja |
|
||
|
|
|--------|---------|
|
||
|
|
| **Fuente de verdad** | KCL modular, único, versionado con código |
|
||
|
|
| **Validación** | Nativa en KCL, compile-time, no runtime |
|
||
|
|
| **Defaults** | Herencia + override en KCL, no duplicados |
|
||
|
|
| **Modularidad** | Imports, composición, reutilización |
|
||
|
|
| **Generación** | On-demand, ephemeral, no ruido en repo |
|
||
|
|
| **Adaptación** | Parametrizada via Tera templates |
|
||
|
|
| **Consumo** | provisioning/provctl/installer adaptan sin cambios |
|
||
|
|
| **Mantenimiento** | Un solo archivo principal, evoluciona con código |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 Implementación: Próximos Pasos
|
||
|
|
|
||
|
|
### Fase 1: Crear módulos KCL
|
||
|
|
1. Crear `syntaxis/configs/schemas.k`
|
||
|
|
2. Crear `syntaxis/configs/defaults.k`
|
||
|
|
3. Crear `syntaxis/configs/services.k`
|
||
|
|
4. Crear `syntaxis/configs/presets.k`
|
||
|
|
5. Crear `syntaxis/configs/deployment.k`
|
||
|
|
|
||
|
|
### Fase 2: Crear templates Tera
|
||
|
|
1. Crear `syntaxis/templates/provisioning-config.j2`
|
||
|
|
2. Crear `syntaxis/templates/provctl-config.j2`
|
||
|
|
3. Crear `syntaxis/templates/installer-settings.j2`
|
||
|
|
|
||
|
|
### Fase 3: Crear scripts NuShell
|
||
|
|
1. Crear `syntaxis/scripts/export-config.nu`
|
||
|
|
|
||
|
|
### Fase 4: Integración
|
||
|
|
1. Validar KCL compila sin errores
|
||
|
|
2. Probar exports: `kcl run syntaxis/configs/deployment.k`
|
||
|
|
3. Extender syntaxis-installer para usar presets
|
||
|
|
4. Testing E2E
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📌 Conclusión
|
||
|
|
|
||
|
|
La arquitectura correcta es:
|
||
|
|
|
||
|
|
- ✅ **KCL modular en syntaxis/configs/** - Única verdad, validación nativa
|
||
|
|
- ❌ **No generar/guardar YAML/TOML** - On-demand, ephemeral
|
||
|
|
- ✅ **Tera templates para adaptación** - Parámetros → valores
|
||
|
|
- ✅ **Consumo adaptativo** - Cada herramienta se adapta
|
||
|
|
|
||
|
|
Resultado: Elegante, mantenible, reutilizable, escalable.
|