sync: workflow CI self-contained + imagen Harbor t-<short> (ADR-061)
Some checks failed
__APP_NAME__ — build & deploy / Calidad + build + push (push) Failing after 11s
__APP_NAME__ — build & deploy / Helm upgrade --install (push) Has been skipped

Workflow <app>-build.yaml self-contained — sin uses: cross-repo al repo
privado coralware/IDP. requirements-dev.txt inline. image.repository apunta
al proyecto Harbor dedicado del tenant t-<tenant-id-short> (ADR-019 D-03).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Coralware Template Sync
2026-05-21 22:08:14 -05:00
parent 785ccbdc79
commit 975673a844
4 changed files with 115 additions and 24 deletions

View File

@@ -1,5 +1,12 @@
name: __APP_NAME__ — build & deploy 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: on:
push: push:
branches: [main] branches: [main]
@@ -7,29 +14,101 @@ on:
- 'code/**' - 'code/**'
- 'helm/**' - 'helm/**'
- '.gitea/workflows/__APP_NAME__-build.yaml' - '.gitea/workflows/__APP_NAME__-build.yaml'
workflow_dispatch: {}
jobs: jobs:
# Tests + build + push a Harbor — patrón ADR-038 reusable workflow. # ── Calidad + build + push de imagen a Harbor ────────────────────────────
build: build:
uses: coralware/IDP/.gitea/workflows/_python-microservice.yaml@main name: Calidad + build + push
with: runs-on: [self-hosted, buildkit]
service_name: __APP_NAME__ outputs:
service_dir: code image_tag: ${{ steps.meta.outputs.tag }}
uses_metrics: false defaults:
coverage_threshold: 80 run:
trivy_exit_code: 0 working-directory: code
enforce_rollback: false steps:
- name: Checkout
uses: actions/checkout@v4
# Deploy al cluster del tenant — Helm upgrade --install (sin hook Alembic). - 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
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 (sin 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: helm-deploy:
name: Helm upgrade --install
needs: build needs: build
runs-on: [self-hosted, linux/amd64] runs-on: [self-hosted, buildkit]
container: alpine/helm:3.14.4
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install kubectl - name: Instalar helm
run: apk add --no-cache kubectl 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 - name: Decodificar kubeconfig del tenant
env: env:
@@ -40,9 +119,9 @@ jobs:
echo "tenant-manager debe provisionarlo al crear el repo." echo "tenant-manager debe provisionarlo al crear el repo."
exit 1 exit 1
fi fi
mkdir -p /root/.kube mkdir -p "${HOME}/.kube"
echo "$KUBECONFIG_B64" | base64 -d > /root/.kube/config echo "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config"
chmod 600 /root/.kube/config chmod 600 "${HOME}/.kube/config"
- name: Helm upgrade --install - name: Helm upgrade --install
env: env:
@@ -67,10 +146,8 @@ jobs:
- name: Smoke check post-deploy - name: Smoke check post-deploy
# FQDN con tenant-id CON guiones — coincide con el A record que crea # FQDN con tenant-id CON guiones — coincide con el A record que crea
# cloudflare-provisioner (Gap 5) y con el host del Ingress NGINX # cloudflare-provisioner y con el host del Ingress NGINX
# (helm/APP_NAME/values.yaml). Antes usaba TENANT_ID_NODASHES, lo # (helm/APP_NAME/values.yaml). Paridad ADR-053 §11.1.
# que no resolvía al A record canónico — corregido APERTURA COVE
# Sesión 3 (TENANT-INGRESS-CONTROLLER-MISMATCH, paridad ADR-053 §11.1).
env: env:
INGRESS_HOST: __APP_NAME__-${{ vars.TENANT_ID }}.apps.coralware.cloud INGRESS_HOST: __APP_NAME__-${{ vars.TENANT_ID }}.apps.coralware.cloud
run: | run: |

View File

@@ -52,7 +52,9 @@ def create_app() -> FastAPI:
app = FastAPI( app = FastAPI(
title="__APP_NAME__", title="__APP_NAME__",
version="0.1.0", version="0.1.0",
dependencies=[Depends(_require_auth_dep)] if settings.OPENAPI_REQUIRE_AUTH else [], dependencies=(
[Depends(_require_auth_dep)] if settings.OPENAPI_REQUIRE_AUTH else []
),
) )
@app.middleware("http") @app.middleware("http")

View File

@@ -1,4 +1,15 @@
# Dependencias de desarrollo y CI del golden path rest-api/python.
# Self-contained (ADR-061): el repo del tenant no tiene acceso al monorepo IDP,
# así que las versiones de tooling se inlinean aquí en vez de referenciar
# code/requirements-dev-base.txt. Mantener alineadas a ese archivo (ADR-038).
-r requirements.txt -r requirements.txt
-r ../../code/requirements-dev-base.txt
# Tooling de calidad — versiones canónicas ADR-038
ruff==0.9.10
black==25.1.0
bandit==1.8.3
pytest==8.3.5
pytest-cov==6.0.0
# Específicas del shape
httpx==0.27.2 httpx==0.27.2

View File

@@ -4,7 +4,8 @@
replicaCount: 1 replicaCount: 1
image: image:
repository: registry.coralsafety.com/__TENANT_ID_NODASHES__/__APP_NAME__ # Proyecto Harbor dedicado del tenant: `t-<tenant-id-short>` (ADR-019 D-03).
repository: registry.coralsafety.com/t-__TENANT_ID_SHORT__/__APP_NAME__
tag: latest tag: latest
pullPolicy: Always pullPolicy: Always
pullSecret: harbor-tenant-pull pullSecret: harbor-tenant-pull