diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 36c98dc..5f2ab38 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -116,12 +116,16 @@ jobs: IMAGE_REPOSITORY: gitea.cruzcloud.net/devops/ecommerce-frontend NAMESPACE: ecommerce DEPLOY_TIMEOUT_SECONDS: '420' - KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }} + K8S_SERVER: ${{ secrets.K8S_SERVER }} + K8S_CA_B64: ${{ secrets.K8S_CA_B64 }} + K8S_TOKEN: ${{ secrets.K8S_TOKEN }} + K8S_TLS_SERVER_NAME: ${{ secrets.K8S_TLS_SERVER_NAME }} run: | set -euo pipefail EXPECTED_IMAGE="${IMAGE_REPOSITORY}:${VERSION}" KUBECONFIG_PATH="${RUNNER_TEMP:-/tmp}/kubeconfig" + CA_PATH="${RUNNER_TEMP:-/tmp}/kubernetes-ca.crt" echo "==================================================" echo "Validación posterior al despliegue" @@ -130,93 +134,110 @@ jobs: echo "Timeout: ${DEPLOY_TIMEOUT_SECONDS} segundos" echo "==================================================" - if [ -z "${KUBE_CONFIG_DATA:-}" ]; then - echo "ERROR: no está configurado el secret KUBE_CONFIG_DATA." + for variable in K8S_SERVER K8S_CA_B64 K8S_TOKEN; do + if [ -z "${!variable:-}" ]; then + echo "ERROR: no está configurado el secret ${variable}." + exit 1 + fi + done + + K8S_SERVER="$( + printf '%s' "${K8S_SERVER}" | + tr -d '\r\n\t ' + )" + + K8S_TOKEN="$( + printf '%s' "${K8S_TOKEN}" | + tr -d '\r\n' + )" + + K8S_CA_B64="$( + printf '%s' "${K8S_CA_B64}" | + tr -d '\r\n\t ' + )" + + if ! printf '%s' "${K8S_SERVER}" | + grep -Eq '^https://[^[:space:]]+:[0-9]+$'; then + echo "ERROR: K8S_SERVER no tiene un formato válido." + echo "Ejemplo: https://192.168.68.61:46421" exit 1 fi - # 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." - - KUBE_CONFIG_B64="$( - printf '%s' "${KUBE_CONFIG_DATA}" | - tr -d '\r\n\t ' - )" - - # Tolera que se haya pegado accidentalmente el prefijo de variable. - KUBE_CONFIG_B64="${KUBE_CONFIG_B64#KUBE_CONFIG_DATA=}" - - 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 comillas, prompts ni texto adicional." - exit 1 - fi - - BASE64_LENGTH="${#KUBE_CONFIG_B64}" - BASE64_REMAINDER=$((BASE64_LENGTH % 4)) - - echo "Longitud Base64 normalizada: ${BASE64_LENGTH}" - echo "Residuo de longitud: ${BASE64_REMAINDER}" - - # El padding '=' puede perderse al copiar el secret. - # Residuo 2 => faltan '=='; residuo 3 => falta '='. - # Residuo 1 no puede repararse: el valor está truncado o dañado. - case "${BASE64_REMAINDER}" in - 0) - ;; - 2) - KUBE_CONFIG_B64="${KUBE_CONFIG_B64}==" - echo "Padding Base64 restaurado automáticamente: ==" - ;; - 3) - KUBE_CONFIG_B64="${KUBE_CONFIG_B64}=" - echo "Padding Base64 restaurado automáticamente: =" - ;; - 1) - echo "ERROR: el Base64 está truncado o dañado." - echo "Una longitud con residuo 1 no puede corregirse agregando padding." - echo "Vuelve a guardar el secret o pégalo directamente como YAML." - exit 1 - ;; - esac - - 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 "Vuelve a crear el secret o utiliza el kubeconfig YAML sin codificar." - exit 1 - fi + if printf '%s' "${K8S_SERVER}" | + grep -Eq '^https://(127\.0\.0\.1|localhost|0\.0\.0\.0)(:|/)'; then + echo "ERROR: K8S_SERVER usa una dirección no accesible desde el contenedor." + exit 1 fi + if ! printf '%s' "${K8S_CA_B64}" | + grep -Eq '^[A-Za-z0-9+/]*={0,2}$'; then + echo "ERROR: K8S_CA_B64 contiene caracteres que no pertenecen a Base64." + exit 1 + fi + + CA_REMAINDER=$(( ${#K8S_CA_B64} % 4 )) + + case "${CA_REMAINDER}" in + 0) + ;; + 2) + K8S_CA_B64="${K8S_CA_B64}==" + ;; + 3) + K8S_CA_B64="${K8S_CA_B64}=" + ;; + 1) + echo "ERROR: K8S_CA_B64 está truncado o dañado." + exit 1 + ;; + esac + + if ! printf '%s' "${K8S_CA_B64}" | + base64 --decode > "${CA_PATH}" 2>/dev/null; then + echo "ERROR: no se pudo decodificar K8S_CA_B64." + exit 1 + fi + + if ! grep -q 'BEGIN CERTIFICATE' "${CA_PATH}"; then + echo "ERROR: K8S_CA_B64 no contiene un certificado PEM." + exit 1 + fi + + chmod 600 "${CA_PATH}" + + { + echo 'apiVersion: v1' + echo 'kind: Config' + echo 'clusters:' + echo '- name: lab-cluster' + echo ' cluster:' + echo " server: ${K8S_SERVER}" + echo " certificate-authority: ${CA_PATH}" + + if [ -n "${K8S_TLS_SERVER_NAME:-}" ]; then + echo " tls-server-name: ${K8S_TLS_SERVER_NAME}" + fi + + echo 'users:' + echo '- name: gitea-postdeploy-validator' + echo ' user:' + echo " token: ${K8S_TOKEN}" + echo 'contexts:' + echo '- name: gitea-postdeploy-validator' + echo ' context:' + echo ' cluster: lab-cluster' + echo ' user: gitea-postdeploy-validator' + echo " namespace: ${NAMESPACE}" + echo 'current-context: gitea-postdeploy-validator' + } > "${KUBECONFIG_PATH}" + 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 + echo "API Server configurado: ${K8S_SERVER}" - 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 + if [ -n "${K8S_TLS_SERVER_NAME:-}" ]; then + echo "TLS server name configurado: ${K8S_TLS_SERVER_NAME}" fi if ! command -v kubectl >/dev/null 2>&1; then @@ -237,18 +258,24 @@ jobs: 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 + echo "$(cat /tmp/kubectl.sha256) /tmp/kubectl" | + sha256sum --check + chmod +x /tmp/kubectl export PATH="/tmp:${PATH}" fi kubectl version --client - if ! kubectl get namespace "${NAMESPACE}" >/dev/null; then - echo "ERROR: no hay conectividad o permisos sobre el namespace ${NAMESPACE}." + if ! kubectl get deployments \ + --namespace "${NAMESPACE}" \ + --request-timeout='15s' >/dev/null; then + echo "ERROR: no hay conectividad o permisos para leer Deployments en ${NAMESPACE}." exit 1 fi + echo "Conectividad y permisos de lectura confirmados." + DEADLINE=$((SECONDS + DEPLOY_TIMEOUT_SECONDS)) DEPLOYMENT='' @@ -320,7 +347,10 @@ jobs: 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 + kubectl get pods \ + --namespace "${NAMESPACE}" \ + --selector "${SELECTOR}" \ + -o wide exit 1 fi @@ -368,4 +398,4 @@ jobs: echo "Deployment: ${DEPLOYMENT}" echo "Pods validados: ${VALIDATED_PODS}/${EXPECTED_REPLICAS}" echo "Tag ejecutado: ${EXPECTED_IMAGE}" - echo "==================================================" + echo "==================================================" \ No newline at end of file