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 primer commit + push, tu desarrollo reemplaza esa página — define tu propio handler de / (en code/app/main.py o en code/app/routers/).

Contrato del shape: ver coralware-shape.yaml en 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

  1. Tú escribes código — endpoints en code/app/routers/, modelos en code/app/models/, migraciones con alembic revision --autogenerate.
  2. git push a main dispara el workflow Gitea Actions.
  3. El CI corre black, ruff, bandit, pytest --cov. Si algo falla, el pipeline aborta.
  4. Si pasa, builda la imagen linux/amd64 con BuildKit y la publica en el proyecto Harbor de tu tenant.
  5. helm upgrade --install aplica el chart al cluster de tu tenant. Antes del rollout, un Job pre-upgrade corre alembic upgrade head. Si Alembic falla, Helm aborta y la app vieja queda intacta.
  6. Tras rollout exitoso, smoke check valida /health 200.

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 concurrency por repo, así que dos git push seguidos se encolan (no corren el helm upgrade en 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 AddonClaimaddon-provisioner (ADR-052 + ADR-064 + ADR-066):

  1. El workflow del tenant aplica manifests/addonclaim.yaml con kubectl apply -f - (placeholders sustituidos con sed). El claim declara addonType: mariadb, databaseName derivado del nombre del release y el secretName esperado.
  2. addon-provisioner (corriendo en el plano de control IDP) reconcilia el AddonClaim: crea una BD lógica + user + grant en la instancia MariaDB compartida del tenant (ns addons del cluster tenant) vía Job DDL efímero.
  3. Publica el Secret __APP_NAME__-db-creds en el ns apps.
  4. El workflow corre helm upgrade --install. El InitContainer wait-for-secret del Job Alembic (hook pre-install,pre-upgrade) espera al Secret (hasta 360s), Alembic ejecuta upgrade head, y el Deployment rola.

Opt-out — apps stateless

Si tu app no necesita BD:

  1. En values.yaml:
    database:
      enabled: false    # no se aplica Role/RoleBinding al SA
    alembic:
      enabled: false    # no corre el Job Alembic
    
  2. Elimina el step "Aplicar AddonClaim" del workflow .gitea/workflows/__APP_NAME__-build.yaml (junto con manifests/addonclaim.yaml y el step pre-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 && git push github main
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

Sin lock-in técnico.


Referencias

  • docs/adr-053-shape-web-backend-python.md — anatomía del shape
  • docs/adr-052-tenant-addon-claim-model.md — modelo de add-ons (BD)
  • docs/adr-064-free-trial-allows-mariadb.md — free-trial permite MariaDB
  • docs/adr-038-ci-estandar-reusable-workflow.md — pipeline Python
Description
Template Golden Path — Web Backend con Python
Readme 255 KiB
Languages
Python 54.9%
Shell 35.7%
HTML 3.9%
Smarty 3.4%
Dockerfile 2.1%