220 lines
8.9 KiB
YAML
220 lines
8.9 KiB
YAML
name: Deploy production
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- master
|
||
workflow_dispatch:
|
||
|
||
concurrency:
|
||
group: production
|
||
cancel-in-progress: false
|
||
|
||
jobs:
|
||
deploy:
|
||
if: gitea.repository == 'Robotail/Corp' && gitea.ref == 'refs/heads/master'
|
||
runs-on: deploy
|
||
timeout-minutes: 60
|
||
steps:
|
||
- name: Deploy current Gitea revision to CORP
|
||
shell: sh
|
||
env:
|
||
CORP_DEPLOY_SSH_KEY: ${{ secrets.CORP_DEPLOY_SSH_KEY }}
|
||
TARGET_SHA: ${{ gitea.sha }}
|
||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
||
run: |
|
||
set -eu
|
||
umask 077
|
||
case "${GITEA_RUN_ID}" in ''|*[!0-9]*) echo 'Invalid run ID.' >&2; exit 2 ;; esac
|
||
case "${TARGET_SHA}" in *[!0-9a-f]*|'') echo 'Invalid revision.' >&2; exit 2 ;; esac
|
||
[ "${#TARGET_SHA}" -eq 40 ] || exit 2
|
||
status_file="${RUNNER_TEMP:-/tmp}/eusrr-production-deploy-${GITEA_RUN_ID}.status"
|
||
printf '%s\n' failure > "${status_file}"
|
||
work_dir="$(mktemp -d)"
|
||
trap 'rm -rf -- "${work_dir}"' EXIT
|
||
trap 'exit 129' HUP
|
||
trap 'exit 130' INT
|
||
trap 'exit 143' TERM
|
||
printf '%s\n' "${CORP_DEPLOY_SSH_KEY}" > "${work_dir}/key"
|
||
unset CORP_DEPLOY_SSH_KEY
|
||
printf '%s\n' '172.11.0.11 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJsNn4tOkIiZgZCWCumPi/OTuB7kSUkFd8u7iJnIFTwx' > "${work_dir}/known_hosts"
|
||
ssh \
|
||
-i "${work_dir}/key" \
|
||
-o BatchMode=yes \
|
||
-o IdentitiesOnly=yes \
|
||
-o ConnectTimeout=10 \
|
||
-o ServerAliveInterval=15 \
|
||
-o ServerAliveCountMax=3 \
|
||
-o StrictHostKeyChecking=yes \
|
||
-o UserKnownHostsFile="${work_dir}/known_hosts" \
|
||
deploy@172.11.0.11 \
|
||
"deploy ${TARGET_SHA}"
|
||
printf '%s\n' success > "${status_file}"
|
||
|
||
- name: Send deployment report to Telegram
|
||
if: always()
|
||
continue-on-error: true
|
||
shell: sh
|
||
env:
|
||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||
DEPLOY_SHA: ${{ gitea.sha }}
|
||
DEPLOY_REF: ${{ gitea.ref_name }}
|
||
DEPLOY_REPOSITORY: ${{ gitea.repository }}
|
||
DEPLOY_ACTOR: ${{ gitea.actor }}
|
||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
||
GITEA_RUN_NUMBER: ${{ gitea.run_number }}
|
||
run: |
|
||
# Reporting must never change the deployment result.
|
||
set +e
|
||
umask 077
|
||
|
||
status_file="${RUNNER_TEMP:-/tmp}/eusrr-production-deploy-${GITEA_RUN_ID}.status"
|
||
if [ -r "${status_file}" ]; then
|
||
DEPLOY_OUTCOME="$(cat "${status_file}" 2>/dev/null)"
|
||
else
|
||
DEPLOY_OUTCOME=unknown
|
||
fi
|
||
|
||
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ] || [ -z "${TELEGRAM_CHAT_ID:-}" ]; then
|
||
echo "Telegram deployment report skipped: TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not configured."
|
||
exit 0
|
||
fi
|
||
case "${DEPLOY_OUTCOME:-unknown}" in
|
||
success)
|
||
heading="✅ Деплой production успешно завершён"
|
||
production_state="Ревизия ${DEPLOY_SHA} активна в production."
|
||
;;
|
||
failure)
|
||
heading="❌ Деплой production завершился ошибкой"
|
||
production_state="Успешный запуск новой ревизии не подтверждён. Проверьте журнал деплоя и результат отката."
|
||
;;
|
||
cancelled)
|
||
heading="⚠️ Деплой production был отменён"
|
||
production_state="Запрошенная ревизия не подтверждена как активная."
|
||
;;
|
||
*)
|
||
heading="⚠️ Деплой production завершён со статусом ${DEPLOY_OUTCOME:-unknown}"
|
||
production_state="Запрошенная ревизия не подтверждена как активная."
|
||
;;
|
||
esac
|
||
|
||
# Gitea 1.23 uses the repository run index (run_number) in web URLs,
|
||
# not the internal database ID (run_id). Keep run_id for status files.
|
||
run_url="${GITEA_SERVER_URL%/}/${DEPLOY_REPOSITORY}/actions/runs/${GITEA_RUN_NUMBER}"
|
||
message="$(printf '%s\n\nРепозиторий: %s\nВетка: %s\nРевизия: %s\nИнициатор: %s\nТекущее состояние: %s\nЗапуск: %s' \
|
||
"${heading}" \
|
||
"${DEPLOY_REPOSITORY}" \
|
||
"${DEPLOY_REF}" \
|
||
"${DEPLOY_SHA}" \
|
||
"${DEPLOY_ACTOR}" \
|
||
"${production_state}" \
|
||
"${run_url}")"
|
||
|
||
response_file="$(mktemp)"
|
||
telegram_url="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
|
||
report_sent=false
|
||
|
||
if command -v curl >/dev/null 2>&1; then
|
||
http_code="$(curl \
|
||
--silent \
|
||
--output "${response_file}" \
|
||
--write-out '%{http_code}' \
|
||
--connect-timeout 5 \
|
||
--max-time 15 \
|
||
--retry 2 \
|
||
--retry-delay 2 \
|
||
--request POST \
|
||
"${telegram_url}" \
|
||
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
|
||
--data-urlencode "text=${message}" \
|
||
--data-urlencode "disable_web_page_preview=true" 2>/dev/null)"
|
||
transport_status=$?
|
||
if [ "${transport_status}" -eq 0 ] \
|
||
&& [ "${http_code}" -ge 200 ] 2>/dev/null \
|
||
&& [ "${http_code}" -lt 300 ] 2>/dev/null; then
|
||
report_sent=true
|
||
else
|
||
echo "Telegram report through curl failed; trying another available transport."
|
||
fi
|
||
fi
|
||
|
||
if [ "${report_sent}" != true ] && command -v wget >/dev/null 2>&1; then
|
||
compact_message="$(printf '%s | Репозиторий: %s | Ветка: %s | Ревизия: %s | Инициатор: %s | Текущее состояние: %s | Запуск: %s' \
|
||
"${heading}" \
|
||
"${DEPLOY_REPOSITORY}" \
|
||
"${DEPLOY_REF}" \
|
||
"${DEPLOY_SHA}" \
|
||
"${DEPLOY_ACTOR}" \
|
||
"${production_state}" \
|
||
"${run_url}")"
|
||
# This fallback must work without Python. JSON-escape user-facing
|
||
# metadata so quotes/backslashes in an actor name cannot break it.
|
||
json_escape() {
|
||
printf '%s' "$1" | tr '\r\n\t' ' ' | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||
}
|
||
telegram_payload="$(printf '{"chat_id":"%s","text":"%s","disable_web_page_preview":true}' \
|
||
"$(json_escape "${TELEGRAM_CHAT_ID}")" "$(json_escape "${compact_message}")")"
|
||
wget \
|
||
-q \
|
||
-O "${response_file}" \
|
||
-T 15 \
|
||
-t 3 \
|
||
--header='Content-Type: application/json' \
|
||
--post-data="${telegram_payload}" \
|
||
"${telegram_url}" 2>/dev/null
|
||
transport_status=$?
|
||
if [ "${transport_status}" -eq 0 ]; then
|
||
report_sent=true
|
||
else
|
||
echo "Telegram report through wget failed; trying another available transport."
|
||
fi
|
||
fi
|
||
|
||
if [ "${report_sent}" != true ] && command -v python3 >/dev/null 2>&1; then
|
||
TELEGRAM_MESSAGE="${message}" python3 - <<'PY'
|
||
import json
|
||
import os
|
||
import urllib.request
|
||
|
||
payload = json.dumps(
|
||
{
|
||
"chat_id": os.environ["TELEGRAM_CHAT_ID"],
|
||
"text": os.environ["TELEGRAM_MESSAGE"],
|
||
"disable_web_page_preview": True,
|
||
}
|
||
).encode()
|
||
request = urllib.request.Request(
|
||
f"https://api.telegram.org/bot{os.environ['TELEGRAM_BOT_TOKEN']}/sendMessage",
|
||
data=payload,
|
||
headers={"Content-Type": "application/json"},
|
||
method="POST",
|
||
)
|
||
try:
|
||
with urllib.request.urlopen(request, timeout=15) as response:
|
||
if not 200 <= response.status < 300:
|
||
raise RuntimeError("Non-successful Telegram response")
|
||
except Exception as exc:
|
||
# Never print the token-bearing request URL or an HTTP body.
|
||
print(f"Telegram Python transport failed ({type(exc).__name__}).")
|
||
raise SystemExit(1) from None
|
||
PY
|
||
transport_status=$?
|
||
if [ "${transport_status}" -eq 0 ]; then
|
||
report_sent=true
|
||
else
|
||
echo "Telegram report through Python failed."
|
||
fi
|
||
fi
|
||
|
||
rm -f "${response_file}"
|
||
|
||
if [ "${report_sent}" = true ]; then
|
||
echo "Telegram deployment report sent."
|
||
else
|
||
echo "Telegram deployment report skipped: no available transport could deliver it."
|
||
fi
|
||
|
||
exit 0
|