Update .gitea/workflows/build.yaml
This commit is contained in:
+228
-14
@@ -1,4 +1,4 @@
|
||||
name: Build and Push Frontend
|
||||
name: Build, Push and Validate Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,13 +11,12 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Descarga el historial necesario para sincronizar origin/main.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Definir Versión Semántica
|
||||
@@ -75,9 +74,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -qE \
|
||||
"image:[[:space:]]*${IMAGE_REPOSITORY}:" \
|
||||
"${MANIFEST}"; then
|
||||
if ! grep -qE "image:[[:space:]]*${IMAGE_REPOSITORY}:" "${MANIFEST}"; then
|
||||
echo "ERROR: no se encontró la imagen ${IMAGE_REPOSITORY} en ${MANIFEST}."
|
||||
echo "Líneas image encontradas:"
|
||||
grep -nE "^[[:space:]]*image:" "${MANIFEST}" || true
|
||||
@@ -95,24 +92,241 @@ jobs:
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "El manifiesto ya contiene la versión ${VERSION}."
|
||||
exit 0
|
||||
break
|
||||
fi
|
||||
|
||||
git commit -m "chore: release ${VERSION} [skip ci]"
|
||||
|
||||
if git push origin HEAD:main; then
|
||||
echo "Manifiesto actualizado correctamente con ${VERSION}."
|
||||
exit 0
|
||||
break
|
||||
fi
|
||||
|
||||
echo "origin/main cambió mientras se realizaba el push."
|
||||
|
||||
if [ "${intento}" -lt 3 ]; then
|
||||
espera=$((intento * 3))
|
||||
echo "Reintentando en ${espera} segundos..."
|
||||
sleep "${espera}"
|
||||
if [ "${intento}" -eq 3 ]; then
|
||||
echo "ERROR: no fue posible actualizar main después de 3 intentos."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
espera=$((intento * 3))
|
||||
echo "Reintentando en ${espera} segundos..."
|
||||
sleep "${espera}"
|
||||
done
|
||||
|
||||
echo "ERROR: no fue posible actualizar main después de 3 intentos."
|
||||
exit 1
|
||||
- name: Validar despliegue y tag ejecutado en Kubernetes
|
||||
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 "=================================================="
|
||||
Reference in New Issue
Block a user