chore(mirror): sync desde templates/rest-api-python (ADR-095)
This commit is contained in:
61
openspec/config.yaml
Normal file
61
openspec/config.yaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
|
||||||
|
context: |
|
||||||
|
Componente: Golden Path "rest-api/python" (Coralware Cove IDP)
|
||||||
|
Tipo: Shape de aplicación tenant — FastAPI stateless puro (sin BD)
|
||||||
|
Repo Gitea generado: idp-templates/template-rest-api-python
|
||||||
|
Audiencia consumidora: tenants externos en tier free-trial / startup
|
||||||
|
Gobernanza: openspec interno de Coralware — NO se sincroniza al repo
|
||||||
|
Gitea ni llega al repo del cliente.
|
||||||
|
|
||||||
|
Stack del shape:
|
||||||
|
- Python 3.12 + FastAPI + Uvicorn
|
||||||
|
- Sin persistencia (BD opcional via web-backend/python si se requiere)
|
||||||
|
- Helm chart minimal (Deployment + Service + Ingress (networking.k8s.io/v1, NGINX) + NetworkPolicy)
|
||||||
|
- CI: Gitea Actions invocando reusable workflow ADR-038 + helm-deploy
|
||||||
|
|
||||||
|
Diferencias con web-backend/python:
|
||||||
|
- SIN alembic, SIN claim-mariadb.yaml, SIN Job pre-upgrade
|
||||||
|
- SIN testcontainers (tests sin Docker)
|
||||||
|
- SIN ServiceMonitor por default (tier free-trial sin Prometheus)
|
||||||
|
- Tests apuntan a probar el endpoint, no a cubrir un dominio rico
|
||||||
|
- Helm chart sin hooks; rollout directo
|
||||||
|
|
||||||
|
Convenciones obligatorias del shape:
|
||||||
|
- Endpoints fijos: /health, /docs, /openapi.json, /metrics
|
||||||
|
- /health retorna {status: "ok"} (sin chequeo de DB)
|
||||||
|
- Labels: app.kubernetes.io/name=<APP_NAME>, idp.coralware.cloud/tier,
|
||||||
|
idp.coralware.cloud/tenant-id, idp.coralware.cloud/golden-path=rest-api,
|
||||||
|
idp.coralware.cloud/golden-path-lang=python
|
||||||
|
- SecurityContext: runAsNonRoot=true, runAsUser=1001, readOnlyRootFilesystem=true,
|
||||||
|
drop ALL caps, allowPrivilegeEscalation=false
|
||||||
|
- Imagen: registry.coralsafety.com/tenant-<id>/<APP_NAME>:<tag>
|
||||||
|
- Pipeline ADR-038: black → ruff → bandit → pytest --cov 80 → buildkit → trivy
|
||||||
|
- Coverage threshold del shape: 80%
|
||||||
|
|
||||||
|
ADRs relacionados:
|
||||||
|
- ADR-027 — Golden Paths Fase 5
|
||||||
|
- ADR-029 — API standards (R-007 correlation_id)
|
||||||
|
- ADR-038 — CI estándar reusable workflow Python
|
||||||
|
- ADR-053 — Anatomía del shape web-backend/python (referencia)
|
||||||
|
|
||||||
|
Tier por default:
|
||||||
|
- free-trial: 1 réplica, sin add-ons, namespace tenant-<id>-apps
|
||||||
|
- startup+: puede escalar réplicas hasta tope del tier
|
||||||
|
|
||||||
|
rules:
|
||||||
|
proposal:
|
||||||
|
- Incluir sección "Non-goals" — qué NO entrega el shape
|
||||||
|
- Si el cambio agrega persistencia, valorar promover a web-backend/python
|
||||||
|
en lugar de complicar este shape
|
||||||
|
- Referenciar ADRs y golden paths cuando aplique
|
||||||
|
design:
|
||||||
|
- Documentar variables de entorno consumidas (LOG_LEVEL, CORRELATION_ID_HEADER,
|
||||||
|
KEYCLOAK_ISSUER opcional)
|
||||||
|
- Comportamiento fail-safe: errores de upstream no crashean la app
|
||||||
|
- Si el cambio toca el flujo CI/CD, actualizar flowchart Mermaid de specs/main.md
|
||||||
|
ANTES de tocar el workflow YAML
|
||||||
|
tasks:
|
||||||
|
- shapeVersion en coralware-shape.yaml sigue semver
|
||||||
|
- Mantener exclusión de openspec/ en el sync a Gitea
|
||||||
|
- Validar render server-side de placeholders por repo-provisioner
|
||||||
153
openspec/specs/main.md
Normal file
153
openspec/specs/main.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Spec — Golden Path `rest-api/python`
|
||||||
|
|
||||||
|
**Versión del shape:** 1.0.0
|
||||||
|
**Status:** Activo (free-trial entry point)
|
||||||
|
**ADR de referencia:** ADR-027 (Golden Paths Fase 5), ADR-053 (anatomía
|
||||||
|
del shape web-backend/python — referencia para decisiones de empaque),
|
||||||
|
**ADR-060 Fase 1 (`GATEWAY-API-MIGRATION`): ingress = Traefik Proxy + Gateway
|
||||||
|
API (`HTTPRoute`), reemplaza el `rke2-ingress-nginx` de ADR-053 §11.1**,
|
||||||
|
ADR-061 (workflow CI self-contained — sin `uses:` cross-repo; `helm-deploy`
|
||||||
|
inline sin `container:`)
|
||||||
|
**Visibilidad:** interna de Coralware — este spec NO se sincroniza al
|
||||||
|
repo Gitea ni al repo del cliente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Propósito
|
||||||
|
|
||||||
|
Definir el comportamiento del shape `rest-api/python` — scaffold de API
|
||||||
|
HTTP stateless en FastAPI, sin persistencia. Es el shape **entry point**
|
||||||
|
para tier `free-trial`: el cliente arranca rápido con un endpoint REST
|
||||||
|
funcional sin tener que decidir BD, migraciones ni hooks.
|
||||||
|
|
||||||
|
Cuando un cliente necesita persistencia, debería **migrar a
|
||||||
|
`web-backend/python`** (ADR-053), no extender este shape para incluir DB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Contrato del shape
|
||||||
|
|
||||||
|
### 2.1 Entradas
|
||||||
|
|
||||||
|
| Entrada | Origen | Descripción |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| `__APP_NAME__` | placeholder | Sustituido server-side por `repo-provisioner` |
|
||||||
|
| `__TENANT_ID__` | placeholder | UUID del tenant |
|
||||||
|
| `__TENANT_ID_NODASHES__` | placeholder | UUID sin guiones, para DNS y namespaces |
|
||||||
|
| `__TIER__` | placeholder | `free-trial` | `startup` | `business` | `enterprise` |
|
||||||
|
| `KUBECONFIG_TENANT_B64` | secret de proyecto Gitea | Provisto por `tenant-manager` |
|
||||||
|
| `vars.TENANT_ID`, `vars.TENANT_ID_NODASHES`, `vars.TENANT_TIER` | vars de proyecto | Provisto por `tenant-manager` |
|
||||||
|
|
||||||
|
### 2.2 Salidas observables
|
||||||
|
|
||||||
|
| Salida | Forma |
|
||||||
|
|--------|-------|
|
||||||
|
| Imagen Docker | `registry.coralsafety.com/t-<tenant-id-short>/<APP_NAME>:<tag>` — proyecto Harbor dedicado del tenant (ADR-019 D-03) |
|
||||||
|
| Helm release | `<APP_NAME>` en namespace `apps` del cluster tenant (ADR-054) |
|
||||||
|
| App reachable | `https://<APP_NAME>.<tenant-id-con-guiones>.apps.coralware.cloud/health` retorna 200 (FQDN subdominio DOT, ADR-067/ADR-082 — reconciliado drift previo que usaba el host plano con guion) |
|
||||||
|
| Ingress controller | **Traefik Proxy + Gateway API** en `traefik-system` (bundle Fleet `traefik-bootstrap`) — ADR-060 Fase 1 (`GATEWAY-API-MIGRATION`); reemplaza `rke2-ingress-nginx` (EOL marzo 2026). El shape emite un `HTTPRoute` adjunto al `Gateway` `tenant-gateway` (ns `apps`, stack-deployer §3c). Paridad con web-backend/python |
|
||||||
|
| TLS | Wildcard per-tenant `*.<tenant-id>.apps.coralware.cloud` vía Secret `tenant-wildcard-tls` (cert-manager local del cluster, ADR-067 — reconciliado drift previo que citaba el `apps-wildcard-tls` compartido de Fase 0) |
|
||||||
|
| Endpoints obligatorios | `/health`, `/docs`, `/openapi.json`, `/metrics` |
|
||||||
|
|
||||||
|
### 2.3 Invariantes
|
||||||
|
|
||||||
|
- App stateless — ningún componente persistente requerido.
|
||||||
|
- `/health` retorna 200 con `{"status":"ok"}` siempre que el proceso esté vivo.
|
||||||
|
- `helm upgrade --install` del chart funciona **sin `--no-hooks`**. El
|
||||||
|
`ServiceAccount` se materializa como hook `pre-install,pre-upgrade`
|
||||||
|
(`hook-weight: -5`, `hook-delete-policy: before-hook-creation`) por paridad
|
||||||
|
con el shape `web-backend/python`. Este shape no tiene Job de migración, así
|
||||||
|
que el hook no es estrictamente necesario — se mantiene para coherencia entre
|
||||||
|
ambos charts.
|
||||||
|
- SecurityContext: `runAsNonRoot=true`, `readOnlyRootFilesystem=true`,
|
||||||
|
`drop ALL caps`.
|
||||||
|
- Sin IPs hardcoded; sin credenciales en código.
|
||||||
|
- Errores de servicios upstream **NO crashean** la app — se reportan en
|
||||||
|
el endpoint que los invoca, el proceso sigue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Flujo principal
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
dev_push([Dev hace git push\nal repo Gitea del tenant]) --> quality_gate[quality_gate\nruff + black + bandit + pytest --cov 80]
|
||||||
|
quality_gate -- fail --> pipeline_failed([pipeline_failed\nHelm no se invoca])
|
||||||
|
quality_gate -- ok --> image_build[image_build\nbuildx push a Harbor t-short]
|
||||||
|
image_build -- fail --> pipeline_failed
|
||||||
|
image_build -- ok --> helm_deploy[helm_deploy\nhelm upgrade --install --wait]
|
||||||
|
helm_deploy --> rollout_apply[rollout_apply\nDeployment con nuevo image tag]
|
||||||
|
rollout_apply --> rollout_status_check[rollout_status_check\nkubectl rollout status timeout 3m]
|
||||||
|
rollout_status_check -- fail --> helm_rollback_manual([helm_rollback_manual\ndev decide rollback\npipeline_failed])
|
||||||
|
rollout_status_check -- ok --> smoke_health_check[smoke_health_check\nwget /health 200]
|
||||||
|
smoke_health_check -- fail --> investigate([investigate\nlogs + events\npipeline_failed])
|
||||||
|
smoke_health_check -- ok --> app_running([app_running])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sin nodo `alembic_hook`** — diferencia clave con `web-backend/python`.
|
||||||
|
Los node IDs son labels Prometheus futuros.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Estados terminales
|
||||||
|
|
||||||
|
| Estado | Significado | Acción del dev |
|
||||||
|
|--------|-------------|----------------|
|
||||||
|
| `app_running` | Todo OK — app desplegada, /health 200 | Ninguna |
|
||||||
|
| `pipeline_failed` | Quality o build falló | Inspeccionar logs CI; corregir; push |
|
||||||
|
| `helm_rollback_manual` | Rollout no estabilizó en 3 min | `helm rollback`; investigar pod events |
|
||||||
|
| `investigate` | Smoke falla aunque rollout terminó | Logs Deployment + cluster events |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Comportamiento fail-safe
|
||||||
|
|
||||||
|
- **Servicio upstream caído:** endpoint responde 503 con detalle, app sigue.
|
||||||
|
- **Imagen no disponible:** `ImagePullBackOff` visible en Rancher; rollout no avanza.
|
||||||
|
- **Variable de entorno faltante (ej. `LOG_LEVEL`):** default seguro, no crash.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Pruebas negativas
|
||||||
|
|
||||||
|
| ID | Prueba | Estado terminal esperado |
|
||||||
|
|----|--------|--------------------------|
|
||||||
|
| N1 | Test fallando | `pipeline_failed` en `quality_gate` |
|
||||||
|
| N2 | Imagen tag inexistente | `ImagePullBackOff` visible en Rancher |
|
||||||
|
| N3 | Probe `/health` removido del pod | Rollout falla → `helm_rollback_manual` |
|
||||||
|
|
||||||
|
(Set reducido vs. web-backend/python; sin pruebas de DB porque no hay BD.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Versionado del shape
|
||||||
|
|
||||||
|
`coralware-shape.yaml` declara `shapeVersion` (semver). Mismas reglas
|
||||||
|
que `web-backend/python` (ver ADR-053 §7).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Sincronización a Gitea
|
||||||
|
|
||||||
|
`templates/rest-api-python/` (este repo IDP) → `idp-templates/template-rest-api-python` (Gitea).
|
||||||
|
|
||||||
|
Exclusiones del sync:
|
||||||
|
|
||||||
|
- `openspec/` — gobernanza interna del golden path.
|
||||||
|
- `.ruff_cache/`, `__pycache__/`, `.pytest_cache/`.
|
||||||
|
|
||||||
|
Mecanismo: `rsync --exclude='openspec/' --exclude-from=.gitignore`
|
||||||
|
desde el scaffold local hacia el clone temporal del repo Gitea.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Promoción a web-backend/python
|
||||||
|
|
||||||
|
Si la app crece y necesita persistencia:
|
||||||
|
|
||||||
|
1. Cliente clona el template `web-backend/python` en un repo nuevo.
|
||||||
|
2. Migra rutas de su app `rest-api` al nuevo scaffold.
|
||||||
|
3. Coralware provisiona el `AddonClaim` MariaDB (manual via runbook hasta
|
||||||
|
ADR-052).
|
||||||
|
|
||||||
|
No hay path de migración in-place — son shapes distintos por diseño.
|
||||||
Reference in New Issue
Block a user