ilopez 81b024edd6
Some checks failed
__APP_NAME__ — build & deploy / Calidad + build + push (push) Failing after 14s
__APP_NAME__ — build & deploy / Helm upgrade --install (push) Has been skipped
fix(chart): NetworkPolicy como Helm hook weight -8 (no resource normal)
Chicken-and-egg encontrado en smoke E2E DO (cluster 91c93c9c):
- ns 'apps' tiene default-deny NetworkPolicy entregada por stack-deployer.
- Job Alembic (pre-install hook) ejecuta InitContainer wait-for-secret que
  hace REST API call al kube-apiserver para chequear Secret.
- La NetworkPolicy del chart (que permite DNS + apiserver egress) estaba como
  RESOURCE NORMAL, asi que solo se aplicaba en la FASE PRINCIPAL del helm
  install, DESPUES de los hooks.
- Resultado: el InitContainer del hook quedaba bloqueado DNS por
  default-deny → curl timeout 240s → helm install fail → NetworkPolicy
  nunca se aplica.

Fix:
- Annotations 'helm.sh/hook: pre-install,pre-upgrade' + 'hook-weight: -8'
  + 'hook-delete-policy: before-hook-creation'.
- Weight ordering: -10 AddonClaim → -8 NetworkPolicy → -7 Role/RoleBinding
  → -5 SA → 0 Job Alembic.
- delete-policy: before-hook-creation (no hook-succeeded): el Deployment de
  la fase principal necesita la NP viva en runtime.
- Comentario en la regla egress :443 aclarando que cubre kube-apiserver
  (10.43.0.1) + Harbor (sin necesidad de regla adicional).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 10:13:03 -05:00

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.

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 con AddonClaim + hook Alembic
└── .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 hooks Helm

El chart usa hooks pre-install,pre-upgrade con hook-weight para garantizar el orden de creación. El helm upgrade --install funciona sin --no-hooks:

Recurso Template hook-weight Motivo
AddonClaim addonclaim.yaml -10 Declara la intención de la app de tener una BD — el primero en aplicarse para que addon-provisioner empiece a reconciliar lo antes posible
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 240s), luego corre alembic upgrade head

El AddonClaim (hook weight -10) NO declara hook-delete-policy intencionalmente: así sobrevive entre helm upgrade (re-apply idempotente) y se evita disparar el finalizer cleanup-logical del controller en cada upgrade (que dropearía el user de la BD y regeneraría el password, causando ventana de downtime). Trade-off: helm uninstall deja el AddonClaim huérfano y MariaDB sigue aprovisionada. Es coherente con deletionPolicy: Retain — borrar BD productiva debe ser decisión explícita, no side-effect del uninstall. Cleanup explícito:

kubectl delete addonclaim <release>-db -n apps

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):

  1. El chart aplica helm/__APP_NAME__/templates/addonclaim.yaml como hook pre-install,pre-upgrade weight -10 — declara addonType: mariadb, databaseName derivado del Release name 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 InitContainer wait-for-secret del Job Alembic espera al Secret (hasta 240s), Alembic ejecuta upgrade head, y el Deployment rola.

Opt-out — apps stateless

Si tu app no necesita BD, en values.yaml:

database:
  enabled: false    # no se aplica AddonClaim ni Role/RoleBinding
alembic:
  enabled: false    # no corre el Job Alembic

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%