mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-22 02:27:57 +00:00
The comment at the top of this workflow says documentation-only changes skip it, because a push to master pushes an image, runs the migrations and restarts the demo container. The ignore list did not cover scripts/k8s, so editing a manifest that the deploy never reads bought the site an outage. The pattern is scripts/k8s/** rather than scripts/** because scripts/Dockerfile is a build input - go.yml builds the release image from it on a tag. This workflow file stays outside the list on purpose. paths-ignore skips only when every changed path matches, so a change that edits the deploy still runs it, which is the point.
169 lines
7.7 KiB
YAML
169 lines
7.7 KiB
YAML
name: Build
|
|
|
|
# Documentation-only changes, and changes confined to the Kubernetes
|
|
# manifests, skip this workflow entirely.
|
|
#
|
|
# A push to master here does not just build - it pushes an image, runs the
|
|
# migrations and restarts the demo container, so the site takes a short outage.
|
|
# Paying that for a README edit is waste at best; at worst a deploy fails for a
|
|
# reason unrelated to anything in the change. Code coverage is unaffected,
|
|
# because go.yml still builds every push and pull request.
|
|
#
|
|
# scripts/k8s holds deploy.yml, storage.yml and prerun.sh, and the deploy below
|
|
# reads none of them - it is an ssh into one host that runs docker, building the
|
|
# Dockerfile at the repository root. Those manifests are for people deploying to
|
|
# a cluster of their own. The pattern is scripts/k8s/** rather than scripts/**
|
|
# because scripts/Dockerfile is a build input: go.yml builds the release image
|
|
# from it on a tag.
|
|
#
|
|
# A file outside these patterns still runs the workflow even when the rest of
|
|
# the change is ignorable: paths-ignore skips only when every changed path
|
|
# matches. Editing this file is one such case, on purpose - a deploy script
|
|
# that is never exercised by the change that broke it is worse than an outage.
|
|
on:
|
|
push:
|
|
branches: [ master ]
|
|
paths-ignore:
|
|
- '**.md'
|
|
- 'docs/**'
|
|
- 'LICENSE*'
|
|
- '.github/ISSUE_TEMPLATE/**'
|
|
- 'scripts/k8s/**'
|
|
pull_request:
|
|
branches: [ master ]
|
|
paths-ignore:
|
|
- '**.md'
|
|
- 'docs/**'
|
|
- 'LICENSE*'
|
|
- '.github/ISSUE_TEMPLATE/**'
|
|
- 'scripts/k8s/**'
|
|
|
|
# One deploy at a time. Two merges seconds apart raced here: both runs did
|
|
# docker rm -f then docker run, the second removed the container the first had
|
|
# just created, and the first's docker run then failed on a name conflict -
|
|
# leaving the demo on the older image with a red deploy.
|
|
concurrency:
|
|
group: deploy-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称
|
|
TAG: ${{ github.sha }}
|
|
IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }}
|
|
|
|
jobs:
|
|
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version: 1.26.5
|
|
|
|
- name: Tidy
|
|
run: go mod tidy
|
|
|
|
- name: Build
|
|
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
|
|
|
|
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
|
|
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
|
|
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
|
|
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
|
|
- name: Build the Docker image and push
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
run: |
|
|
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
|
echo "************ docker login end"
|
|
docker build -t go-admin-api:latest .
|
|
echo "************ docker build end"
|
|
docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }}
|
|
echo "************ docker tag end"
|
|
docker images
|
|
echo "************ docker images end"
|
|
docker push ${{ env.IMAGE_NAME_TAG }} # 推送
|
|
echo "************ docker push end"
|
|
|
|
- name: Restart server # 第五步,重启服务
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
|
|
env:
|
|
GITHUB_SHA_X: ${GITHUB_SHA}
|
|
with:
|
|
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
|
|
username: ${{ secrets.SSH_USERNAME }}
|
|
key: ${{ secrets.DEPLOY_KEY }}
|
|
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
|
|
#
|
|
# 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库,
|
|
# 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。
|
|
# 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。
|
|
#
|
|
# 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的
|
|
# 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。
|
|
#
|
|
# 顺序是有意的:迁移先跑,跑不过就保持现有版本不动;
|
|
# 旧容器改名保留而不是删除,新容器不健康时能原样恢复。
|
|
# 健康检查两条都要过——HTTP 活着不代表数据库通了。
|
|
script: |
|
|
set -u
|
|
CFG="${{ secrets.DEMO_CONFIG_PATH }}"
|
|
IMG="${{ env.IMAGE_NAME_TAG }}"
|
|
NAME=go-admin-api
|
|
PREV="$NAME-prev"
|
|
|
|
test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; }
|
|
|
|
sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
|
sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; }
|
|
|
|
# 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的;
|
|
# 硬切过去才会得到代码与表对不上的服务。
|
|
if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \
|
|
/main migrate -c /config/settings.yml; then
|
|
echo "迁移失败,保持现有版本"; exit 1
|
|
fi
|
|
|
|
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then
|
|
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
|
sudo docker rename "$NAME" "$PREV"
|
|
# --timeout, because the default is 10 seconds and the process
|
|
# spends drain + server + cleanup from extend.shutdown before it
|
|
# exits - 8 seconds out of the box, and more for anyone who
|
|
# configures a drain window. Past the deadline docker sends
|
|
# SIGKILL and the cleanup callbacks are cut off part-way through.
|
|
# checksilent's docker-stop-cuts-shutdown-short check compares
|
|
# this number against config/settings.yml.
|
|
sudo docker stop --timeout 30 "$PREV" >/dev/null
|
|
fi
|
|
|
|
sudo docker run -d -p 8000:8000 \
|
|
-v "$CFG":/config/settings.yml:ro \
|
|
--name "$NAME" "$IMG"
|
|
|
|
ok=0
|
|
for i in $(seq 1 20); do
|
|
sleep 3
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true)
|
|
if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then
|
|
ok=1; echo "健康检查通过(第 $i 次探测)"; break
|
|
fi
|
|
done
|
|
|
|
if [ "$ok" = "1" ]; then
|
|
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
|
else
|
|
echo "健康检查失败,回滚到上一版本"
|
|
sudo docker logs --tail 40 "$NAME" 2>&1 || true
|
|
sudo docker rm -f "$NAME" >/dev/null 2>&1 || true
|
|
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then
|
|
sudo docker rename "$PREV" "$NAME"
|
|
sudo docker start "$NAME" >/dev/null
|
|
echo "已恢复"
|
|
fi
|
|
exit 1
|
|
fi
|