Update .gitea/workflows/build.yaml

This commit is contained in:
2026-07-12 07:54:45 +00:00
parent 02c079d16a
commit aea3a598c9
+228 -14
View File
@@ -1,4 +1,4 @@
name: Build and Push Frontend name: Build, Push and Validate Frontend
on: on:
push: push:
@@ -11,13 +11,12 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 25
steps: steps:
- name: Checkout del código - name: Checkout del código
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
# Descarga el historial necesario para sincronizar origin/main.
fetch-depth: 0 fetch-depth: 0
- name: Definir Versión Semántica - name: Definir Versión Semántica
@@ -75,9 +74,7 @@ jobs:
exit 1 exit 1
fi fi
if ! grep -qE \ if ! grep -qE "image:[[:space:]]*${IMAGE_REPOSITORY}:" "${MANIFEST}"; then
"image:[[:space:]]*${IMAGE_REPOSITORY}:" \
"${MANIFEST}"; then
echo "ERROR: no se encontró la imagen ${IMAGE_REPOSITORY} en ${MANIFEST}." echo "ERROR: no se encontró la imagen ${IMAGE_REPOSITORY} en ${MANIFEST}."
echo "Líneas image encontradas:" echo "Líneas image encontradas:"
grep -nE "^[[:space:]]*image:" "${MANIFEST}" || true grep -nE "^[[:space:]]*image:" "${MANIFEST}" || true
@@ -95,24 +92,241 @@ jobs:
if git diff --cached --quiet; then if git diff --cached --quiet; then
echo "El manifiesto ya contiene la versión ${VERSION}." echo "El manifiesto ya contiene la versión ${VERSION}."
exit 0 break
fi fi
git commit -m "chore: release ${VERSION} [skip ci]" git commit -m "chore: release ${VERSION} [skip ci]"
if git push origin HEAD:main; then if git push origin HEAD:main; then
echo "Manifiesto actualizado correctamente con ${VERSION}." echo "Manifiesto actualizado correctamente con ${VERSION}."
exit 0 break
fi fi
echo "origin/main cambió mientras se realizaba el push." echo "origin/main cambió mientras se realizaba el push."
if [ "${intento}" -lt 3 ]; then if [ "${intento}" -eq 3 ]; then
espera=$((intento * 3)) echo "ERROR: no fue posible actualizar main después de 3 intentos."
echo "Reintentando en ${espera} segundos..." exit 1
sleep "${espera}"
fi fi
espera=$((intento * 3))
echo "Reintentando en ${espera} segundos..."
sleep "${espera}"
done done
echo "ERROR: no fue posible actualizar main después de 3 intentos." - name: Validar despliegue y tag ejecutado en Kubernetes
exit 1 shell: bash
env:
VERSION: ${{ steps.vars.outputs.VERSION }}
IMAGE_REPOSITORY: gitea.cruzcloud.net/devops/ecommerce-frontend
NAMESPACE: ecommerce
DEPLOY_TIMEOUT_SECONDS: '420'
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
run: |
set -euo pipefail
EXPECTED_IMAGE="${IMAGE_REPOSITORY}:${VERSION}"
echo "=================================================="
echo "Validación posterior al despliegue"
echo "Namespace: ${NAMESPACE}"
echo "Imagen esperada: ${EXPECTED_IMAGE}"
echo "Timeout: ${DEPLOY_TIMEOUT_SECONDS} segundos"
echo "=================================================="
if [ -z "${KUBE_CONFIG_DATA:-}" ]; then
echo "ERROR: no está configurado el secret KUBE_CONFIG_DATA."
exit 1
fi
mkdir -p "${HOME}/.kube"
printf '%s' "${KUBE_CONFIG_DATA}" | base64 --decode > "${HOME}/.kube/config"
chmod 600 "${HOME}/.kube/config"
export KUBECONFIG="${HOME}/.kube/config"
# Instala kubectl únicamente si el runner no lo incluye.
if ! command -v kubectl >/dev/null 2>&1; then
case "$(uname -m)" in
x86_64) KUBECTL_ARCH="amd64" ;;
aarch64|arm64) KUBECTL_ARCH="arm64" ;;
*)
echo "ERROR: arquitectura no soportada para kubectl: $(uname -m)"
exit 1
;;
esac
KUBECTL_VERSION="$(curl -fsSL https://dl.k8s.io/release/stable.txt)"
echo "Instalando kubectl ${KUBECTL_VERSION} (${KUBECTL_ARCH})..."
curl -fsSLo /tmp/kubectl \
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${KUBECTL_ARCH}/kubectl"
curl -fsSLo /tmp/kubectl.sha256 \
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${KUBECTL_ARCH}/kubectl.sha256"
echo "$(cat /tmp/kubectl.sha256) /tmp/kubectl" | sha256sum --check
chmod +x /tmp/kubectl
export PATH="/tmp:${PATH}"
fi
kubectl version --client
# También valida conectividad y permisos mínimos sobre el namespace.
kubectl get deployments --namespace "${NAMESPACE}" >/dev/null
# Argo CD sincroniza de forma asíncrona. Se espera hasta que algún
# Deployment del namespace tenga exactamente la imagen publicada.
DEADLINE=$((SECONDS + DEPLOY_TIMEOUT_SECONDS))
DEPLOYMENT=""
while [ "${SECONDS}" -lt "${DEADLINE}" ]; do
MATCHES=()
while IFS= read -r candidate; do
[ -n "${candidate}" ] || continue
candidate_images="$(
kubectl get deployment "${candidate}" \
--namespace "${NAMESPACE}" \
--output jsonpath='{.spec.template.spec.containers[*].image}'
)"
for image in ${candidate_images}; do
if [ "${image}" = "${EXPECTED_IMAGE}" ]; then
MATCHES+=("${candidate}")
break
fi
done
done < <(
kubectl get deployments \
--namespace "${NAMESPACE}" \
--output jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'
)
if [ "${#MATCHES[@]}" -eq 1 ]; then
DEPLOYMENT="${MATCHES[0]}"
echo "Deployment actualizado por Argo CD: ${DEPLOYMENT}"
break
fi
if [ "${#MATCHES[@]}" -gt 1 ]; then
echo "ERROR: más de un Deployment usa ${EXPECTED_IMAGE}: ${MATCHES[*]}"
exit 1
fi
echo "Argo CD aún no ha aplicado ${EXPECTED_IMAGE}. Reintentando en 10 segundos..."
sleep 10
done
if [ -z "${DEPLOYMENT}" ]; then
echo "ERROR: Argo CD no aplicó ${EXPECTED_IMAGE} dentro del tiempo límite."
echo "Imágenes actuales en los Deployments de ${NAMESPACE}:"
kubectl get deployments \
--namespace "${NAMESPACE}" \
--output custom-columns='DEPLOYMENT:.metadata.name,IMAGES:.spec.template.spec.containers[*].image'
exit 1
fi
echo "Esperando que finalice el rollout de deployment/${DEPLOYMENT}..."
kubectl rollout status "deployment/${DEPLOYMENT}" \
--namespace "${NAMESPACE}" \
--timeout="${DEPLOY_TIMEOUT_SECONDS}s"
CONTAINER_NAME="$(
kubectl get deployment "${DEPLOYMENT}" \
--namespace "${NAMESPACE}" \
--output go-template="{{range .spec.template.spec.containers}}{{if eq .image \"${EXPECTED_IMAGE}\"}}{{.name}}{{end}}{{end}}"
)"
if [ -z "${CONTAINER_NAME}" ]; then
echo "ERROR: no se pudo determinar el contenedor que usa ${EXPECTED_IMAGE}."
exit 1
fi
SELECTOR="$(
kubectl get deployment "${DEPLOYMENT}" \
--namespace "${NAMESPACE}" \
--output go-template='{{range $key, $value := .spec.selector.matchLabels}}{{printf "%s=%s," $key $value}}{{end}}' \
| sed 's/,$//'
)"
if [ -z "${SELECTOR}" ]; then
echo "ERROR: no se pudo obtener el selector de deployment/${DEPLOYMENT}."
exit 1
fi
echo "Contenedor objetivo: ${CONTAINER_NAME}"
echo "Selector de Pods: ${SELECTOR}"
EXPECTED_REPLICAS="$(
kubectl get deployment "${DEPLOYMENT}" \
--namespace "${NAMESPACE}" \
--output jsonpath='{.spec.replicas}'
)"
# Solo incluye Pods activos: Running y sin deletionTimestamp.
# Así no se toma como error un Pod antiguo que todavía esté terminando.
mapfile -t PODS < <(
kubectl get pods \
--namespace "${NAMESPACE}" \
--selector "${SELECTOR}" \
--output go-template='{{range .items}}{{if not .metadata.deletionTimestamp}}{{if eq .status.phase "Running"}}{{.metadata.name}}{{"\n"}}{{end}}{{end}}{{end}}'
)
if [ "${#PODS[@]}" -eq 0 ]; then
echo "ERROR: no se encontraron Pods Running para deployment/${DEPLOYMENT}."
exit 1
fi
VALIDATED_PODS=0
for pod in "${PODS[@]}"; do
[ -n "${pod}" ] || continue
POD_IMAGE="$(
kubectl get pod "${pod}" \
--namespace "${NAMESPACE}" \
--output go-template="{{range .spec.containers}}{{if eq .name \"${CONTAINER_NAME}\"}}{{.image}}{{end}}{{end}}"
)"
POD_READY="$(
kubectl get pod "${pod}" \
--namespace "${NAMESPACE}" \
--output go-template="{{range .status.containerStatuses}}{{if eq .name \"${CONTAINER_NAME}\"}}{{.ready}}{{end}}{{end}}"
)"
IMAGE_ID="$(
kubectl get pod "${pod}" \
--namespace "${NAMESPACE}" \
--output go-template="{{range .status.containerStatuses}}{{if eq .name \"${CONTAINER_NAME}\"}}{{.imageID}}{{end}}{{end}}"
)"
echo "Pod: ${pod}"
echo " Ready: ${POD_READY}"
echo " Imagen declarada: ${POD_IMAGE}"
echo " Image ID: ${IMAGE_ID}"
if [ "${POD_IMAGE}" != "${EXPECTED_IMAGE}" ]; then
echo "ERROR: ${pod} ejecuta ${POD_IMAGE}; se esperaba ${EXPECTED_IMAGE}."
exit 1
fi
if [ "${POD_READY}" != "true" ]; then
echo "ERROR: el contenedor ${CONTAINER_NAME} del Pod ${pod} no está Ready."
exit 1
fi
VALIDATED_PODS=$((VALIDATED_PODS + 1))
done
if [ "${VALIDATED_PODS}" -ne "${EXPECTED_REPLICAS}" ]; then
echo "ERROR: se esperaban ${EXPECTED_REPLICAS} Pods activos, pero se validaron ${VALIDATED_PODS}."
kubectl get pods --namespace "${NAMESPACE}" --selector "${SELECTOR}" --output wide
exit 1
fi
echo "=================================================="
echo "VALIDACIÓN EXITOSA"
echo "Deployment: ${DEPLOYMENT}"
echo "Pods validados: ${VALIDATED_PODS}/${EXPECTED_REPLICAS}"
echo "Tag ejecutado: ${EXPECTED_IMAGE}"
echo "=================================================="