11 KiB
APP_NAME — Web Backend Python (FastAPI + Alembic)
App generada desde el Golden Path web-backend/python de Coralware Cove IDP.
Este repositorio contiene el scaffold completo: aplicación FastAPI, migraciones
Alembic, frontend estático en /demo, Helm chart, workflow de CI y manifiesto
de contrato con la plataforma. Cuando se materializa este template para tu
tenant, los placeholders en mayúsculas se sustituyen automáticamente.
Página de bienvenida: al provisionarse, tu plataforma sirve una página de bienvenida en
/con instrucciones de uso. En cuanto hagas tu primercommit+push, tu desarrollo reemplaza esa página — define tu propio handler de/(encode/app/main.pyo encode/app/routers/).
Contrato del shape: ver
coralware-shape.yamlen la raíz. Define los endpoints obligatorios, la BD esperada y la versión del shape. No elimines ese archivo — la plataforma lo lee.
Estructura
.
├── coralware-shape.yaml ← contrato con la plataforma
├── catalog-info.yaml ← entidad Backstage
├── code/
│ ├── app/ ← FastAPI app
│ ├── alembic/ ← migraciones DB
│ ├── tests/ ← pytest + cobertura
│ ├── pyproject.toml
│ ├── requirements.txt
│ └── Dockerfile
├── helm/__APP_NAME__/ ← Helm chart (Deployment, Job Alembic, NP, RBAC)
├── manifests/
│ └── addonclaim.yaml ← AddonClaim fuera del chart (ADR-066)
├── scripts/
│ └── pre-helm-detach.sh ← migración v1.0.0 → v1.1.0 (idempotente)
└── .gitea/workflows/ ← CI: tests + build + deploy
Ciclo de vida
- Tú escribes código — endpoints en
code/app/routers/, modelos encode/app/models/, migraciones conalembic revision --autogenerate. git pushamaindispara el workflow Gitea Actions.- El CI corre
black,ruff,bandit,pytest --cov. Si algo falla, el pipeline aborta. - Si pasa, builda la imagen
linux/amd64con BuildKit y la publica en el proyecto Harbor de tu tenant. helm upgrade --installaplica el chart al cluster de tu tenant. Antes del rollout, un Job pre-upgrade correalembic upgrade head. Si Alembic falla, Helm aborta y la app vieja queda intacta.- Tras rollout exitoso, smoke check valida
/health200.
Orden de aplicación — AddonClaim + hooks Helm
El workflow del tenant sigue este orden en cada git push:
| Paso | Acción | Motivo |
|---|---|---|
| 1 | bash scripts/pre-helm-detach.sh |
Migración chart 1.0.0 → 1.1.0: si el AddonClaim tiene labels/annotations de un release Helm previo, las quita. Idempotente: skip si no aplica. Sunset tras ~2 semanas. |
| 2 | kubectl apply -f manifests/addonclaim.yaml (con sed para placeholders) |
Crea o actualiza el AddonClaim. Idempotente. addon-provisioner empieza a reconciliar en paralelo. |
| 3 | helm upgrade --install <release> ./helm/<APP_NAME> --wait --timeout 10m |
Aplica el chart Helm. Los hooks pre-install,pre-upgrade corren en orden de hook-weight (ver tabla abajo). El Job Alembic espera al Secret. |
Deploys serializados + auto-recuperación: el workflow tiene un guard
concurrencypor repo, así que dosgit pushseguidos se encolan (no corren elhelm upgradeen paralelo). Si un deploy quedó a medias (cancelado), el siguiente run detecta y recupera el release atascado (pending-install→ reinstala;pending-upgrade→ rollback) antes de reintentar.
El chart usa hooks pre-install,pre-upgrade con hook-weight para garantizar
el orden de creación de SUS recursos (sin AddonClaim — ese vive fuera).
helm upgrade --install funciona sin --no-hooks:
| Recurso | Template | hook-weight |
Motivo |
|---|---|---|---|
NetworkPolicy |
networkpolicy.yaml |
-8 |
Bajo el default-deny del namespace apps, esta NP permite DNS + apiserver + addons:3306. Debe existir antes que el InitContainer del Job Alembic intente conectarse al apiserver. |
Role + RoleBinding |
role-db-secret-reader.yaml |
-7 |
Permite al SA leer el Secret <APP_NAME>-db-creds (lo consume el InitContainer wait-for-secret del Job Alembic) |
ServiceAccount |
serviceaccount.yaml |
-5 |
Lo referencia el pod del Job Alembic — debe existir antes |
Job Alembic |
job-alembic-upgrade.yaml |
0 |
InitContainer wait-for-secret espera al Secret publicado por addon-provisioner (hasta 360s — 3× RECONCILE_INTERVAL 60s), luego corre alembic upgrade head |
Por qué el AddonClaim vive FUERA del chart (ADR-066)
En chart 0.2.x el AddonClaim era hook pre-install,pre-upgrade weight -10.
El default de Helm 3 (hook-delete-policy: before-hook-creation) borraba el
claim previo antes de aplicar el nuevo en cada helm upgrade --install. El
finalizer addon-provisioner.idp.coralware.cloud/cleanup-logical ejecutaba
entonces REVOKE + DROP USER + delete del Secret en cada upgrade → rotación
involuntaria de credenciales + release atorado en pending-upgrade.
Convertir el AddonClaim a manifest principal del chart (no hook) también
falla: los hooks pre-install,pre-upgrade corren ANTES del manifest principal,
así que el Job Alembic intentaría leer el Secret antes de que el AddonClaim
exista — chicken-and-egg fatal.
La solución es desacoplar el lifecycle del CRD del lifecycle del release Helm:
el AddonClaim lo aplica el workflow con kubectl apply (idempotente, sin
ownership Helm), y Helm gobierna sólo la app + sus hooks Alembic/NP/RBAC.
helm uninstall no toca el AddonClaim — desacoplamiento garantizado.
Borrar la BD lógica es decisión explícita:
kubectl delete addonclaim <release>-db -n apps
(Con deletionPolicy: Retain — default — preserva la BD lógica; el operador
emite Event OrphanLogicalDatabase. Cambiar a Delete previo para DROP DATABASE.)
Por qué el ServiceAccount es hook
El ServiceAccount se materializa como hook (no en la fase principal de Helm)
porque el Job Alembic, que también es hook, declara serviceAccountName. Si el
SA viviera en la fase principal se crearía después del Job → el pod del Job
nunca arrancaría (serviceaccount not found) → helm --wait haría timeout.
El SA-hook usa hook-delete-policy: before-hook-creation (no hook-succeeded):
el Deployment lo necesita vivo en runtime, así que solo se borra/recrea al
inicio del siguiente hook, manteniéndolo idempotente entre upgrades.
El imagePullSecret lo provee la plataforma (stack-deployer), no el chart.
El Secret <APP_NAME>-db-creds lo publica addon-provisioner tras reconciliar
el AddonClaim (ver §Base de datos).
Endpoints obligatorios
| Path | Auth | Propósito |
|---|---|---|
/health |
none | Liveness + readiness; devuelve {status: ok, db: up|down} |
/docs |
configurable (OPENAPI_REQUIRE_AUTH) |
Swagger UI |
/openapi.json |
igual que /docs |
Schema OpenAPI |
/demo |
none | Frontend estático servido desde code/app/static/demo/ |
/metrics |
restringido vía NetworkPolicy | Métricas Prometheus |
/health, /docs, /demo son contrato del shape — NO renombres.
Base de datos
Tu app espera un Secret __APP_NAME__-db-creds en su namespace con las
claves DATABASE_URL / DATABASE_HOST / DATABASE_PORT / DATABASE_NAME /
DATABASE_USER / DATABASE_PASSWORD (y opcionalmente MIGRATION_DATABASE_URL).
El Secret lo materializa automáticamente la plataforma vía el flujo
AddonClaim → addon-provisioner (ADR-052 + ADR-064 + ADR-066):
- El workflow del tenant aplica
manifests/addonclaim.yamlconkubectl apply -f -(placeholders sustituidos consed). El claim declaraaddonType: mariadb,databaseNamederivado del nombre del release y elsecretNameesperado. addon-provisioner(corriendo en el plano de control IDP) reconcilia elAddonClaim: crea una BD lógica + user + grant en la instancia MariaDB compartida del tenant (nsaddonsdel cluster tenant) vía Job DDL efímero.- Publica el Secret
__APP_NAME__-db-credsen el nsapps. - El workflow corre
helm upgrade --install. El InitContainerwait-for-secretdel Job Alembic (hookpre-install,pre-upgrade) espera al Secret (hasta 360s), Alembic ejecutaupgrade head, y el Deployment rola.
Opt-out — apps stateless
Si tu app no necesita BD:
- En
values.yaml:database: enabled: false # no se aplica Role/RoleBinding al SA alembic: enabled: false # no corre el Job Alembic - Elimina el step "Aplicar AddonClaim" del workflow
.gitea/workflows/__APP_NAME__-build.yaml(junto conmanifests/addonclaim.yamly el steppre-helm-detach).
Con database.enabled=false la app se despliega sin BD ni migraciones.
Variables de entorno
| Variable | Origen | Default | Descripción |
|---|---|---|---|
DATABASE_URL |
Secret | — | Connection string runtime |
MIGRATION_DATABASE_URL |
Secret | = DATABASE_URL |
Connection para Alembic |
LOG_LEVEL |
ConfigMap | INFO |
DEBUG para troubleshooting |
CORRELATION_ID_HEADER |
ConfigMap | X-Correlation-ID |
Header de trazabilidad |
OPENAPI_REQUIRE_AUTH |
ConfigMap | false |
Activar JWT en /docs |
KEYCLOAK_ISSUER |
ConfigMap | — | URL del realm cuando auth activado |
STATIC_DEMO_PATH |
ConfigMap | /app/static/demo |
Path al frontend estático |
Migraciones Alembic
Migración base vacía en code/alembic/versions/0001_initial.py. Para crear
una nueva tras agregar modelos:
cd code
alembic revision --autogenerate -m "agregar tabla users"
Desarrollo local
cd code
pip install -r requirements-dev.txt
docker run --rm -d --name mariadb-dev \
-e MARIADB_ROOT_PASSWORD=dev -e MARIADB_DATABASE=app \
-p 3306:3306 mariadb:11
export DATABASE_URL="mysql+pymysql://root:dev@127.0.0.1:3306/app"
export MIGRATION_DATABASE_URL="$DATABASE_URL"
alembic upgrade head
uvicorn app.main:app --reload --port 8000
Plan de salida
Si decides migrar fuera de Cove, todo lo que necesitas vive en formato estándar:
| Componente | Cómo lo exportas |
|---|---|
| Código | git remote add github <tu-repo-github> && git push github main (espejo externo a GitHub) |
| Imagen | docker pull desde Harbor + push a tu registry |
| Manifests | Ya están en helm/ — helm template genera YAML aplicable |
| Datos | Coralware ejecuta mariadb-dump y entrega .sql.gz |
Nota: el remote
githubde esta tabla es un espejo externo de salida (tu source of truth fuera de Cove), no el remote de despliegue. El primer push del dev hacia Cove usa el remotecove(git push cove main), que dispara el CI/CD — ver la guía de onboarding (welcome page del repo y email post-active).
Sin lock-in técnico.
Referencias
docs/adr-053-shape-web-backend-python.md— anatomía del shapedocs/adr-052-tenant-addon-claim-model.md— modelo de add-ons (BD)docs/adr-064-free-trial-allows-mariadb.md— free-trial permite MariaDBdocs/adr-038-ci-estandar-reusable-workflow.md— pipeline Python