208 lines
8.1 KiB
YAML
Executable File
208 lines
8.1 KiB
YAML
Executable File
name: Preview Cleanup
|
||
on:
|
||
pull_request:
|
||
types:
|
||
- closed
|
||
branches:
|
||
- main
|
||
- develop
|
||
permissions:
|
||
contents: read
|
||
pull-requests: write
|
||
jobs:
|
||
cleanup-preview:
|
||
name: Cleanup Preview Environment
|
||
runs-on: runtime-builder
|
||
timeout-minutes: 10
|
||
steps:
|
||
- name: Checkout code
|
||
shell: sh
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
run: |
|
||
set -eu
|
||
python3 - <<'PY'
|
||
import io, os, tarfile, time, urllib.request, urllib.error
|
||
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
|
||
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
|
||
last_err = None
|
||
for attempt in range(5):
|
||
try:
|
||
with urllib.request.urlopen(request, timeout=120) as response:
|
||
archive = response.read()
|
||
break
|
||
except urllib.error.HTTPError as e:
|
||
last_err = e
|
||
if e.code >= 500 and attempt < 4:
|
||
wait = 2 ** attempt
|
||
print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||
time.sleep(wait)
|
||
continue
|
||
raise
|
||
except Exception as e:
|
||
last_err = e
|
||
if attempt < 4:
|
||
wait = 2 ** attempt
|
||
print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||
time.sleep(wait)
|
||
continue
|
||
raise
|
||
else:
|
||
raise last_err
|
||
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
|
||
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
|
||
for member in tar.getmembers():
|
||
name = member.name
|
||
if name == root_prefix[:-1]:
|
||
continue
|
||
if name.startswith(root_prefix):
|
||
member.name = name[len(root_prefix):]
|
||
if member.name:
|
||
tar.extract(member, '.')
|
||
PY
|
||
|
||
- name: Extract PR number
|
||
shell: sh
|
||
run: |
|
||
set -eu
|
||
# 优先从event payload中读取(兼容所有PR事件类型)
|
||
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "$GITHUB_EVENT_PATH" ]; then
|
||
PR_NUMBER=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))" < "$GITHUB_EVENT_PATH")
|
||
fi
|
||
# fallback: 从GITHUB_REF中提取
|
||
if [ -z "${PR_NUMBER:-}" ]; then
|
||
PR_NUMBER=$(echo "$GITHUB_REF" | sed -n 's|refs/pull/\([0-9]*\)/.*|\1|p')
|
||
fi
|
||
# 再fallback: 兼容纯数字ref
|
||
if [ -z "${PR_NUMBER:-}" ] || ! echo "$PR_NUMBER" | grep -qE '^[0-9]+$'; then
|
||
echo "WARNING: Could not extract PR number cleanly, using raw ref suffix"
|
||
PR_NUMBER=$(echo "$GITHUB_REF" | sed 's|refs/pull/||; s|/.*||')
|
||
fi
|
||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||
echo "PR number: $PR_NUMBER"
|
||
echo "Preview dir: /var/www/preview/pr-${PR_NUMBER}"
|
||
|
||
- name: Install SSH client
|
||
shell: sh
|
||
run: |
|
||
set -eu
|
||
# 先检查是否已存在ssh
|
||
if command -v ssh >/dev/null 2>&1 && command -v ssh-keyscan >/dev/null 2>&1; then
|
||
echo "SSH client already available: $(ssh -V 2>&1)"
|
||
exit 0
|
||
fi
|
||
# 尝试多种包管理器安装
|
||
if command -v apk >/dev/null 2>&1; then
|
||
apk add --no-cache openssh-client >/dev/null 2>&1
|
||
echo "openssh-client installed via apk"
|
||
elif command -v apt-get >/dev/null 2>&1; then
|
||
apt-get update -qq && apt-get install -y -qq openssh-client >/dev/null 2>&1
|
||
echo "openssh-client installed via apt-get"
|
||
elif command -v yum >/dev/null 2>&1; then
|
||
yum install -y openssh-clients >/dev/null 2>&1
|
||
echo "openssh-client installed via yum"
|
||
elif command -v dnf >/dev/null 2>&1; then
|
||
dnf install -y openssh-clients >/dev/null 2>&1
|
||
echo "openssh-client installed via dnf"
|
||
else
|
||
echo "ERROR: No package manager found and ssh not pre-installed"
|
||
which ssh 2>/dev/null || echo " ssh: not found"
|
||
which ssh-keyscan 2>/dev/null || echo " ssh-keyscan: not found"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Remove preview directory from server
|
||
shell: sh
|
||
env:
|
||
PREVIEW_SSH_HOST: ${{ secrets.PREVIEW_SSH_HOST }}
|
||
PREVIEW_SSH_USER: ${{ secrets.PREVIEW_SSH_USER }}
|
||
PREVIEW_SSH_PORT: ${{ secrets.PREVIEW_SSH_PORT }}
|
||
PREVIEW_SSH_KEY: ${{ secrets.PREVIEW_SSH_KEY }}
|
||
run: |
|
||
set -eux
|
||
preview_host="${PREVIEW_SSH_HOST:-47.98.113.167}"
|
||
preview_user="${PREVIEW_SSH_USER:-deploy}"
|
||
preview_port="${PREVIEW_SSH_PORT:-22222}"
|
||
preview_dir="/var/www/preview/pr-${PR_NUMBER}"
|
||
|
||
mkdir -p ~/.ssh
|
||
|
||
# 查找可用的SSH密钥(优先用 secret 里专门为 preview 配置的 key)
|
||
key_path=""
|
||
if [ -n "${PREVIEW_SSH_KEY:-}" ]; then
|
||
key_path="$HOME/.ssh/id_ed25519"
|
||
printf '%s\n' "$PREVIEW_SSH_KEY" > "$key_path"
|
||
chmod 600 "$key_path"
|
||
echo "Using key from PREVIEW_SSH_KEY secret"
|
||
elif [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
|
||
key_path="/root/.ssh/xiaoxia_runtime_builder"
|
||
echo "Using key: $key_path (builder key)"
|
||
elif [ -f "$HOME/.ssh/xiaoxia_runtime_builder" ]; then
|
||
key_path="$HOME/.ssh/xiaoxia_runtime_builder"
|
||
echo "Using key: $key_path (home key)"
|
||
else
|
||
echo "ERROR: No SSH key available"
|
||
ls -la ~/.ssh/ 2>/dev/null || true
|
||
ls -la /root/.ssh/ 2>/dev/null || true
|
||
exit 1
|
||
fi
|
||
|
||
ssh-keyscan -p "$preview_port" -H "$preview_host" >> ~/.ssh/known_hosts 2>/dev/null
|
||
echo "SSH keyscan done"
|
||
|
||
# 测试SSH连接
|
||
ssh -p "$preview_port" -i "$key_path" -o StrictHostKeyChecking=no "${preview_user}@${preview_host}" "echo SSH_CONNECTION_OK && hostname"
|
||
echo "SSH connection verified"
|
||
|
||
# 检查目录是否存在
|
||
DIR_EXISTS=$(ssh -p "$preview_port" -i "$key_path" -o StrictHostKeyChecking=no "${preview_user}@${preview_host}" \
|
||
"if [ -d '${preview_dir}' ]; then echo 'yes'; else echo 'no'; fi")
|
||
|
||
if [ "$DIR_EXISTS" = "yes" ]; then
|
||
echo "Removing preview directory: ${preview_dir}"
|
||
ssh -p "$preview_port" -i "$key_path" -o StrictHostKeyChecking=no "${preview_user}@${preview_host}" \
|
||
"rm -rf ${preview_dir} && echo 'Preview directory removed successfully'"
|
||
echo "Cleanup completed: ${preview_dir}"
|
||
else
|
||
echo "Preview directory does not exist: ${preview_dir}, nothing to clean up"
|
||
fi
|
||
|
||
- name: Comment cleanup notice on PR
|
||
if: success()
|
||
shell: sh
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
run: |
|
||
set -eu
|
||
# 从event payload读取PR号(最可靠)
|
||
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "$GITHUB_EVENT_PATH" ]; then
|
||
PR_NUMBER=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))" < "$GITHUB_EVENT_PATH")
|
||
else
|
||
PR_NUMBER=$(echo "$GITHUB_REF" | sed -n 's|refs/pull/\([0-9]*\)/.*|\1|p')
|
||
fi
|
||
export PR_NUMBER
|
||
|
||
COMMENT_BODY=$(python3 scripts/ci/preview_comment.py cleanup)
|
||
|
||
API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments"
|
||
curl -s -X POST \
|
||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$COMMENT_BODY" \
|
||
"$API_URL" \
|
||
> /dev/null
|
||
echo "Cleanup comment posted"
|
||
|
||
- name: Report CI trace
|
||
if: always()
|
||
shell: sh
|
||
env:
|
||
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
||
run: |
|
||
STATUS="ok"
|
||
[ ${{ job.status }} = "success" ] || STATUS="error"
|
||
START_TIME=""
|
||
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
||
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
||
|