Files
template-web-backend-python/.gitea/workflows/APP_NAME-build.yaml
iilc 13b9fae61c
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
chart 0.3.0: AddonClaim FUERA del chart Helm (ADR-066 del IDP)
Bug en chart 0.2.x: AddonClaim era Helm hook pre-install,pre-upgrade
weight -10 SIN hook-delete-policy. El default Helm 3 es
before-hook-creation → borra el claim en cada upgrade → finalizer
cleanup-logical rotaba credenciales + atoraba release en pending-upgrade.

Cambios:
- helm/APP_NAME/templates/addonclaim.yaml eliminado.
- manifests/addonclaim.yaml creado con placeholders sed.
- scripts/pre-helm-detach.sh idempotente para migracion v1.0.0 → 1.1.0.
- .gitea/workflows/APP_NAME-build.yaml: 2 nuevos steps detach + apply
  AddonClaim entre kubeconfig y helm upgrade.
- Chart.yaml bump 0.2.0 → 0.3.0.
- README reescrito (3 pasos workflow + tabla hooks sin AddonClaim +
  seccion Por que FUERA del chart + opt-out actualizado).
- Comentarios stale del AddonClaim como hook -10 limpiados en
  networkpolicy.yaml, role-db-secret-reader.yaml, job-alembic-upgrade.yaml.

ADR-066 del IDP documenta la decision arquitectural completa.
2026-05-25 14:28:17 -05:00

203 lines
8.7 KiB
YAML

name: __APP_NAME__ — build & deploy
# Workflow self-contained del golden path (ADR-061): calidad + build + push +
# helm-deploy inline, SIN `uses:` cross-repo al repo privado `coralware/IDP`.
# El repo del tenant es un repo cliente — su CI no comparte el ciclo de release
# del IDP ni el GitOps de Fleet; se construye leyendo solo su propio repo.
# Corre sobre los runners `[self-hosted, buildkit]` de ci-system (executor=host,
# buildkit remoto — sin Docker daemon local; ADR-043).
on:
push:
branches: [main]
paths:
- 'code/**'
- 'helm/**'
- 'manifests/**'
- 'scripts/**'
- '.gitea/workflows/__APP_NAME__-build.yaml'
workflow_dispatch: {}
jobs:
# ── Calidad + build + push de imagen a Harbor ────────────────────────────
build:
name: Calidad + build + push
runs-on: [self-hosted, buildkit]
outputs:
image_tag: ${{ steps.meta.outputs.tag }}
defaults:
run:
working-directory: code
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Crear venv e instalar dependencias
# --system-site-packages reutiliza ruff/black/bandit/pytest ya
# preinstalados en la imagen del runner; el venv añade las deps de la app.
run: |
python3 -m venv --system-site-packages /tmp/venv-__APP_NAME__
/tmp/venv-__APP_NAME__/bin/pip install --no-cache-dir -r requirements-dev.txt
- name: Lint (ruff)
run: ruff check app
- name: Formato (black --check)
run: black --check app
- name: Seguridad (bandit)
run: bandit -c pyproject.toml -r app --severity-level medium
- name: Tests con cobertura
# Los tests corren sobre SQLite efímero (ver code/tests/conftest.py) —
# el runner no tiene Docker daemon. El deploy real usa MariaDB.
run: |
/tmp/venv-__APP_NAME__/bin/python -m pytest tests/ -v \
--cov=app --cov-report=term-missing --cov-fail-under=80
- name: Metadata de imagen
id: meta
run: echo "tag=${{ gitea.sha }}" >> "$GITHUB_OUTPUT"
- name: Build y push a Harbor
# Proyecto Harbor del tenant: `t-<tenant-id-short>` (ADR-019 D-03).
# Credenciales: robot scoped push/pull SOLO a ese proyecto, sembrado
# por tenant-manager en el repo del tenant (paso 3 del reconcile).
env:
REGISTRY: registry.coralsafety.com
ROBOT_USER: ${{ secrets.HARBOR_ROBOT_USER }}
ROBOT_TOKEN: ${{ secrets.HARBOR_ROBOT_TOKEN }}
IMAGE: t-__TENANT_ID_SHORT__/__APP_NAME__
TAG: ${{ steps.meta.outputs.tag }}
run: |
if [ -z "$ROBOT_USER" ] || [ -z "$ROBOT_TOKEN" ]; then
echo "ERROR: secrets HARBOR_ROBOT_USER/HARBOR_ROBOT_TOKEN no configurados."
echo "tenant-manager debe sembrarlos al crear el repo (paso 3 del reconcile)."
exit 1
fi
printf '%s' "$ROBOT_TOKEN" | docker login "$REGISTRY" -u "$ROBOT_USER" --password-stdin
# Builder buildx remoto — apunta al buildkit daemon de ci-system
# (el runner no tiene Docker daemon local).
if ! docker buildx inspect tenant-builder >/dev/null 2>&1; then
docker buildx create --name tenant-builder --driver remote --use \
"${BUILDKIT_HOST:-tcp://buildkitd.ci-system.svc.cluster.local:2375}"
else
docker buildx use tenant-builder
fi
docker buildx build --platform linux/amd64 \
-t "${REGISTRY}/${IMAGE}:${TAG}" \
--push .
# ── Deploy al cluster del tenant — Helm upgrade --install con hook Alembic ─
# El runner corre executor=host: los steps se ejecutan directo sobre el pod
# runner, sin `container:`. helm se instala en runtime.
helm-deploy:
name: Helm upgrade --install
needs: build
runs-on: [self-hosted, buildkit]
steps:
- uses: actions/checkout@v4
- name: Instalar helm
run: |
HELM_VERSION=v3.14.4
case "$(uname -m)" in
x86_64) ARCH=amd64 ;;
aarch64) ARCH=arm64 ;;
*) echo "Arquitectura no soportada: $(uname -m)"; exit 1 ;;
esac
mkdir -p "${HOME}/.local/bin"
curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" \
| tar -xz -C /tmp "linux-${ARCH}/helm"
mv "/tmp/linux-${ARCH}/helm" "${HOME}/.local/bin/helm"
chmod +x "${HOME}/.local/bin/helm"
echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
"${HOME}/.local/bin/helm" version
- name: Decodificar kubeconfig del tenant
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_TENANT_B64 }}
run: |
if [ -z "$KUBECONFIG_B64" ]; then
echo "ERROR: secret KUBECONFIG_TENANT_B64 no configurado en el proyecto Gitea."
echo "tenant-manager debe provisionarlo al crear el repo."
exit 1
fi
mkdir -p "${HOME}/.kube"
echo "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config"
chmod 600 "${HOME}/.kube/config"
# ── ADR-066: AddonClaim vive FUERA del chart Helm ───────────────────
# El claim lo gestiona addon-provisioner via finalizer cleanup-logical;
# no debe acoplarse al lifecycle del release Helm para no rotar
# credenciales en cada upgrade.
- name: De-adoptar AddonClaim legacy (migracion chart 1.0.0 -> 1.1.0)
# Idempotente: skip silencioso si AddonClaim no existe (primera
# instalacion) o si ya esta de-adoptado (segundo run post-migracion).
# Si tiene annotations Helm de un release v1.0.0, las quita para que
# el siguiente helm upgrade no intente borrarlo (no esta en el chart).
# Sunset del step tras una rotacion completa (~2 semanas).
run: |
RELEASE_NAME=__APP_NAME__ bash scripts/pre-helm-detach.sh
- name: Aplicar AddonClaim
# Sustitucion de placeholders con sed (evita dependencia de envsubst,
# que no esta garantizado en la imagen del runner). kubectl apply es
# idempotente: primer push lo crea, push subsiguientes son no-op si
# el spec no cambia. helm upgrade --install nunca lo trackea ni lo borra.
env:
RELEASE_NAME: __APP_NAME__
run: |
# databaseName = release name con guiones a underscores + sufijo _db.
# Coincide con el default del chart 1.0.0 (.Release.Name | replace
# "-" "_" | printf "%s_db").
DATABASE_NAME="$(echo "${RELEASE_NAME}" | tr '-' '_')_db"
sed -e "s|\${RELEASE_NAME}|${RELEASE_NAME}|g" \
-e "s|\${DATABASE_NAME}|${DATABASE_NAME}|g" \
manifests/addonclaim.yaml | kubectl apply -f -
- name: Helm upgrade --install
env:
TENANT_ID: ${{ vars.TENANT_ID }}
TENANT_ID_NODASHES: ${{ vars.TENANT_ID_NODASHES }}
TENANT_TIER: ${{ vars.TENANT_TIER }}
IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
# ADR-054: en cluster tenant los namespaces son funcionales (apps/addons),
# sin tenant-id en el nombre — el cluster ya es el sandbox del tenant.
run: |
helm upgrade --install __APP_NAME__ ./helm/__APP_NAME__ \
--namespace apps \
--create-namespace \
--set image.tag=${IMAGE_TAG} \
--set tenantId=${TENANT_ID} \
--set tenantIdNodashes=${TENANT_ID_NODASHES} \
--set tier=${TENANT_TIER} \
--wait --timeout 7m
# 7m absorbe el peor caso del reconcile de addon-provisioner
# (RECONCILE_INTERVAL 300s + initial_delay 60s) + StatefulSet MariaDB
# ready (~60s) + Job DDL (~10s) + InitContainer wait-for-secret + Alembic.
# AddonClaim ya fue aplicado en el step previo (ADR-066).
- name: Verificar rollout
run: |
kubectl -n apps rollout status deploy/__APP_NAME__ --timeout=3m
- name: Smoke check post-deploy
# FQDN con tenant-id CON guiones — coincide con el A record que crea
# cloudflare-provisioner y con el host del Ingress NGINX
# (helm/APP_NAME/values.yaml).
env:
INGRESS_HOST: __APP_NAME__-${{ vars.TENANT_ID }}.apps.coralware.cloud
run: |
for i in $(seq 1 12); do
if wget -q -O - --timeout=10 "https://${INGRESS_HOST}/health" | grep -q '"status":"ok"'; then
echo "Smoke OK: /health responde 200 con db:up"
exit 0
fi
echo "Intento ${i}/12 — /health aún no responde, esperando 5s…"
sleep 5
done
echo "ERROR: /health no respondió 200 dentro del timeout"
exit 1