From 910554ead31ae3a7324f75cc6a222aac3335efe7 Mon Sep 17 00:00:00 2001 From: devops Date: Sun, 12 Jul 2026 16:25:24 +0000 Subject: [PATCH] Update .gitea/workflows/build.yaml ci: corregir validacion postdeploy y kubeconfig" --- .gitea/workflows/build.yaml | 201 ++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 113 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index b93e690..6b76a86 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -7,11 +7,11 @@ on: paths: - 'workloads/ecommerce/index.html' - 'workloads/ecommerce/Dockerfile' + - '.gitea/workflows/build.yaml' jobs: build: runs-on: ubuntu-latest - timeout-minutes: 25 steps: - name: Checkout del código @@ -25,8 +25,8 @@ jobs: run: | set -euo pipefail - VERSION="v1.0.${{ github.run_number }}" - echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT" + VERSION="v1.0.${{ gitea.run_number }}" + echo "VERSION=${VERSION}" >> "${GITHUB_OUTPUT}" echo "Versión generada: ${VERSION}" - name: Login en Gitea Registry @@ -57,8 +57,6 @@ jobs: git config user.name "Cristian Felipe" git config user.email "cristiancruz0529@gmail.com" - # El repositorio remoto puede cambiar mientras se construye la imagen. - # Se reintenta desde el último origin/main sin usar push --force. for intento in 1 2 3; do echo "--------------------------------------------------" echo "Intento ${intento}/3" @@ -70,14 +68,13 @@ jobs: git checkout -B main origin/main if [ ! -f "${MANIFEST}" ]; then - echo "ERROR: no existe el archivo ${MANIFEST}." + echo "ERROR: no existe ${MANIFEST}." exit 1 fi 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 + echo "ERROR: no se encontró ${IMAGE_REPOSITORY} en ${MANIFEST}." + grep -nE '^[[:space:]]*image:' "${MANIFEST}" || true exit 1 fi @@ -85,13 +82,13 @@ jobs: "s|(image:[[:space:]]*${IMAGE_REPOSITORY}:).*|\\1${VERSION}|g" \ "${MANIFEST}" - echo "Imagen resultante en el manifiesto:" - grep -nE "^[[:space:]]*image:" "${MANIFEST}" || true + echo "Imagen resultante:" + grep -nE '^[[:space:]]*image:' "${MANIFEST}" || true git add "${MANIFEST}" if git diff --cached --quiet; then - echo "El manifiesto ya contiene la versión ${VERSION}." + echo "El manifiesto ya contiene ${VERSION}." break fi @@ -102,15 +99,13 @@ jobs: break fi - echo "origin/main cambió mientras se realizaba el push." - 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..." + echo "origin/main cambió. Reintentando en ${espera} segundos..." sleep "${espera}" done @@ -126,6 +121,7 @@ jobs: set -euo pipefail EXPECTED_IMAGE="${IMAGE_REPOSITORY}:${VERSION}" + KUBECONFIG_PATH="${RUNNER_TEMP:-/tmp}/kubeconfig" echo "==================================================" echo "Validación posterior al despliegue" @@ -139,57 +135,71 @@ jobs: exit 1 fi - mkdir -p "${HOME}/.kube" + # Admite dos formatos para KUBE_CONFIG_DATA: + # 1) kubeconfig YAML completo, o 2) kubeconfig codificado en Base64. + if printf '%s' "${KUBE_CONFIG_DATA}" | grep -qE '^[[:space:]]*apiVersion:[[:space:]]*v1'; then + echo "Formato del secret detectado: kubeconfig YAML." + printf '%s\n' "${KUBE_CONFIG_DATA}" > "${KUBECONFIG_PATH}" + else + echo "Formato del secret detectado: Base64." - # Normaliza saltos de línea, CRLF, espacios o tabulaciones que - # hayan podido introducirse al copiar el secret en la interfaz. - KUBE_CONFIG_B64="$( - printf '%s' "${KUBE_CONFIG_DATA}" \ - | tr -d '\r\n\t ' - )" + KUBE_CONFIG_B64="$( + printf '%s' "${KUBE_CONFIG_DATA}" | + tr -d '\r\n\t ' + )" - if [ -z "${KUBE_CONFIG_B64}" ]; then - echo "ERROR: KUBE_CONFIG_DATA quedó vacío después de normalizarlo." + if [ -z "${KUBE_CONFIG_B64}" ]; then + echo "ERROR: el secret quedó vacío después de normalizarlo." + exit 1 + fi + + if ! printf '%s' "${KUBE_CONFIG_B64}" | + grep -Eq '^[A-Za-z0-9+/]*={0,2}$'; then + echo "ERROR: KUBE_CONFIG_DATA contiene caracteres que no son Base64." + echo "No pegues KUBE_CONFIG_DATA=, comillas, prompts ni encabezados." + exit 1 + fi + + if [ $(( ${#KUBE_CONFIG_B64} % 4 )) -ne 0 ]; then + echo "ERROR: la longitud del Base64 no es múltiplo de 4." + echo "El valor probablemente está incompleto." + exit 1 + fi + + if ! printf '%s' "${KUBE_CONFIG_B64}" | + base64 --decode > "${KUBECONFIG_PATH}" 2>/tmp/kubeconfig-base64.err; then + echo "ERROR: no se pudo decodificar KUBE_CONFIG_DATA como Base64." + echo "Regenera el valor con: base64 -w 0 kubeconfig-gitea.yaml" + exit 1 + fi + fi + + chmod 600 "${KUBECONFIG_PATH}" + export KUBECONFIG="${KUBECONFIG_PATH}" + + if ! grep -qE '^[[:space:]]*apiVersion:[[:space:]]*v1' "${KUBECONFIG}" \ + || ! grep -qE '^[[:space:]]*clusters:' "${KUBECONFIG}" \ + || ! grep -qE '^[[:space:]]*contexts:' "${KUBECONFIG}"; then + echo "ERROR: el secret no produce un kubeconfig válido." exit 1 fi - if ! printf '%s' "${KUBE_CONFIG_B64}" \ - | grep -Eq '^[A-Za-z0-9+/]*={0,2}$'; then - echo "ERROR: KUBE_CONFIG_DATA contiene caracteres que no pertenecen a Base64." - echo "Vuelve a generar el secret codificando el archivo kubeconfig completo." + API_SERVER="$(awk '/^[[:space:]]*server:/ {print $2; exit}' "${KUBECONFIG}")" + echo "API Server configurado: ${API_SERVER:-no detectado}" + + if printf '%s' "${API_SERVER}" | + grep -Eq '^https?://(127\.0\.0\.1|localhost|0\.0\.0\.0)(:|/)'; then + echo "ERROR: el API Server usa una dirección loopback no accesible desde el contenedor del job." + echo "Usa una IP o nombre DNS alcanzable desde el act_runner." exit 1 fi - if [ $(( ${#KUBE_CONFIG_B64} % 4 )) -ne 0 ]; then - echo "ERROR: la longitud de KUBE_CONFIG_DATA no es válida para Base64." - echo "Probablemente el valor fue copiado de forma incompleta." - exit 1 - fi - - if ! printf '%s' "${KUBE_CONFIG_B64}" \ - | base64 --decode > "${HOME}/.kube/config"; then - echo "ERROR: no fue posible decodificar KUBE_CONFIG_DATA." - exit 1 - fi - - chmod 600 "${HOME}/.kube/config" - export KUBECONFIG="${HOME}/.kube/config" - - if ! grep -q '^apiVersion:' "${KUBECONFIG}" \ - || ! grep -q '^clusters:' "${KUBECONFIG}" \ - || ! grep -q '^contexts:' "${KUBECONFIG}"; then - echo "ERROR: el contenido decodificado no parece ser un kubeconfig válido." - echo "No pegues el YAML sin codificar ni una cadena ya recortada." - exit 1 - fi - - # 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" ;; + x86_64) KUBECTL_ARCH='amd64' ;; + aarch64|arm64) KUBECTL_ARCH='arm64' ;; *) - echo "ERROR: arquitectura no soportada para kubectl: $(uname -m)" + echo "ERROR: arquitectura no soportada: $(uname -m)" exit 1 ;; esac @@ -209,39 +219,23 @@ jobs: kubectl version --client - # También valida conectividad y permisos mínimos sobre el namespace. - kubectl get deployments --namespace "${NAMESPACE}" >/dev/null + if ! kubectl get namespace "${NAMESPACE}" >/dev/null; then + echo "ERROR: no hay conectividad o permisos sobre el namespace ${NAMESPACE}." + exit 1 + fi - # 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="" + 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 < <( + mapfile -t MATCHES < <( kubectl get deployments \ --namespace "${NAMESPACE}" \ - --output jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' + --output go-template="{{range .items}}{{\$name := .metadata.name}}{{range .spec.template.spec.containers}}{{if eq .image \"${EXPECTED_IMAGE}\"}}{{\$name}}{{\"\\n\"}}{{end}}{{end}}{{end}}" | + sort -u ) - if [ "${#MATCHES[@]}" -eq 1 ]; then + if [ "${#MATCHES[@]}" -eq 1 ] && [ -n "${MATCHES[0]}" ]; then DEPLOYMENT="${MATCHES[0]}" echo "Deployment actualizado por Argo CD: ${DEPLOYMENT}" break @@ -258,14 +252,12 @@ jobs: 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" @@ -277,33 +269,23 @@ jobs: )" if [ -z "${CONTAINER_NAME}" ]; then - echo "ERROR: no se pudo determinar el contenedor que usa ${EXPECTED_IMAGE}." + echo "ERROR: no se encontró el contenedor con ${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/,$//' + --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}" \ @@ -311,23 +293,22 @@ jobs: --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}." + if [ "${#PODS[@]}" -ne "${EXPECTED_REPLICAS}" ]; then + echo "ERROR: se esperaban ${EXPECTED_REPLICAS} Pods Running y se encontraron ${#PODS[@]}." + kubectl get pods --namespace "${NAMESPACE}" --selector "${SELECTOR}" -o wide exit 1 fi VALIDATED_PODS=0 for pod in "${PODS[@]}"; do - [ -n "${pod}" ] || continue - - POD_IMAGE="$( + DECLARED_IMAGE="$( kubectl get pod "${pod}" \ --namespace "${NAMESPACE}" \ --output go-template="{{range .spec.containers}}{{if eq .name \"${CONTAINER_NAME}\"}}{{.image}}{{end}}{{end}}" )" - POD_READY="$( + READY="$( kubectl get pod "${pod}" \ --namespace "${NAMESPACE}" \ --output go-template="{{range .status.containerStatuses}}{{if eq .name \"${CONTAINER_NAME}\"}}{{.ready}}{{end}}{{end}}" @@ -340,29 +321,23 @@ jobs: )" echo "Pod: ${pod}" - echo " Ready: ${POD_READY}" - echo " Imagen declarada: ${POD_IMAGE}" + echo " Ready: ${READY}" + echo " Imagen declarada: ${DECLARED_IMAGE}" echo " Image ID: ${IMAGE_ID}" - if [ "${POD_IMAGE}" != "${EXPECTED_IMAGE}" ]; then - echo "ERROR: ${pod} ejecuta ${POD_IMAGE}; se esperaba ${EXPECTED_IMAGE}." + if [ "${DECLARED_IMAGE}" != "${EXPECTED_IMAGE}" ]; then + echo "ERROR: ${pod} ejecuta ${DECLARED_IMAGE}, no ${EXPECTED_IMAGE}." exit 1 fi - if [ "${POD_READY}" != "true" ]; then - echo "ERROR: el contenedor ${CONTAINER_NAME} del Pod ${pod} no está Ready." + if [ "${READY}" != 'true' ]; then + echo "ERROR: el contenedor ${CONTAINER_NAME} de ${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}"