Skip to content

Commit 700fa81

Browse files
feat: add container release pipeline
1 parent 9f026c4 commit 700fa81

16 files changed

Lines changed: 721 additions & 87 deletions

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,18 @@ logs/
3636
# 配置文件(将通过挂载传入)
3737
.env
3838
.env.*
39+
config.toml
3940
config/.env
4041
src/config/roles.yaml
4142

43+
# 运行时数据绝不能进入镜像构建上下文
44+
data/*
45+
!data/init_db.sql
46+
*.db
47+
*.db-*
48+
*.sqlite
49+
*.sqlite3
50+
4251
# 文档
4352
docs/
4453

.github/workflows/ci.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
concurrency:
14+
group: ci-${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
tests:
19+
name: Python tests
20+
runs-on: ubuntu-24.04
21+
timeout-minutes: 30
22+
steps:
23+
- name: Check out repository
24+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
28+
with:
29+
version: 0.11.19
30+
enable-cache: true
31+
cache-dependency-glob: uv.lock
32+
33+
- name: Install Python 3.13
34+
run: uv python install 3.13
35+
36+
- name: Install locked dependencies
37+
run: uv sync --frozen --dev
38+
39+
- name: Validate lock file
40+
run: uv lock --check
41+
42+
- name: Compile Python sources
43+
run: uv run python -m compileall -q main.py src scripts
44+
45+
- name: Run tests
46+
run: uv run pytest -q
47+
48+
container-smoke:
49+
name: Container smoke test (amd64)
50+
needs: tests
51+
runs-on: ubuntu-24.04
52+
timeout-minutes: 90
53+
steps:
54+
- name: Check out repository
55+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
56+
57+
- name: Set up Docker Buildx
58+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
59+
60+
- name: Build smoke-test image
61+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
62+
with:
63+
context: .
64+
load: true
65+
platforms: linux/amd64
66+
push: false
67+
tags: al1s:ci-amd64
68+
build-args: AL1S_VERSION=0.1.0
69+
cache-from: type=gha,scope=ci-amd64
70+
cache-to: type=gha,mode=max,scope=ci-amd64
71+
72+
- name: Run image smoke test
73+
run: docker run --rm --platform linux/amd64 al1s:ci-amd64 python scripts/container_healthcheck.py --smoke
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
name: Publish container
2+
3+
on:
4+
release:
5+
types:
6+
- published
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: Version without the v prefix; must match pyproject.toml
11+
required: true
12+
default: 0.1.0
13+
type: string
14+
publish:
15+
description: Push the tested image to GHCR
16+
required: true
17+
default: false
18+
type: boolean
19+
publish_latest:
20+
description: Also update the latest tag
21+
required: true
22+
default: false
23+
type: boolean
24+
25+
permissions:
26+
contents: read
27+
28+
concurrency:
29+
group: publish-container-${{ github.event.release.tag_name || inputs.version || github.run_id }}
30+
cancel-in-progress: false
31+
32+
jobs:
33+
prepare:
34+
name: Validate release metadata
35+
runs-on: ubuntu-24.04
36+
timeout-minutes: 5
37+
outputs:
38+
version: ${{ steps.release.outputs.version }}
39+
series: ${{ steps.release.outputs.series }}
40+
publish: ${{ steps.release.outputs.publish }}
41+
publish_latest: ${{ steps.release.outputs.publish_latest }}
42+
steps:
43+
- name: Check out repository
44+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
45+
46+
- name: Validate version and trigger
47+
id: release
48+
shell: bash
49+
env:
50+
EVENT_NAME: ${{ github.event_name }}
51+
RELEASE_TAG: ${{ github.event.release.tag_name }}
52+
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
53+
INPUT_VERSION: ${{ inputs.version }}
54+
INPUT_PUBLISH: ${{ inputs.publish }}
55+
INPUT_PUBLISH_LATEST: ${{ inputs.publish_latest }}
56+
run: |
57+
set -euo pipefail
58+
59+
if [[ "${EVENT_NAME}" == "release" ]]; then
60+
version="${RELEASE_TAG#v}"
61+
publish=true
62+
if [[ "${RELEASE_PRERELEASE}" == "true" ]]; then
63+
publish_latest=false
64+
else
65+
publish_latest=true
66+
fi
67+
else
68+
version="${INPUT_VERSION#v}"
69+
publish="${INPUT_PUBLISH}"
70+
publish_latest="${INPUT_PUBLISH_LATEST}"
71+
fi
72+
73+
if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
74+
echo "Version must use stable SemVer X.Y.Z: ${version}" >&2
75+
exit 1
76+
fi
77+
78+
project_version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
79+
if [[ "${version}" != "${project_version}" ]]; then
80+
echo "Release version ${version} does not match pyproject.toml ${project_version}" >&2
81+
exit 1
82+
fi
83+
84+
if [[ "${publish_latest}" == "true" && "${publish}" != "true" ]]; then
85+
echo "publish_latest requires publish=true" >&2
86+
exit 1
87+
fi
88+
89+
echo "version=${version}" >> "${GITHUB_OUTPUT}"
90+
echo "series=${version%.*}" >> "${GITHUB_OUTPUT}"
91+
echo "publish=${publish}" >> "${GITHUB_OUTPUT}"
92+
echo "publish_latest=${publish_latest}" >> "${GITHUB_OUTPUT}"
93+
94+
tests:
95+
name: Python tests
96+
needs: prepare
97+
runs-on: ubuntu-24.04
98+
timeout-minutes: 30
99+
steps:
100+
- name: Check out repository
101+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
102+
103+
- name: Install uv
104+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
105+
with:
106+
version: 0.11.19
107+
enable-cache: true
108+
cache-dependency-glob: uv.lock
109+
110+
- name: Install Python 3.13
111+
run: uv python install 3.13
112+
113+
- name: Install locked dependencies
114+
run: uv sync --frozen --dev
115+
116+
- name: Validate and test
117+
run: |
118+
uv lock --check
119+
uv run python -m compileall -q main.py src scripts
120+
uv run pytest -q
121+
122+
smoke:
123+
name: Smoke test (${{ matrix.platform }})
124+
needs:
125+
- prepare
126+
- tests
127+
runs-on: ubuntu-24.04
128+
timeout-minutes: 120
129+
strategy:
130+
fail-fast: false
131+
matrix:
132+
include:
133+
- platform: linux/amd64
134+
arch: amd64
135+
- platform: linux/arm64
136+
arch: arm64
137+
steps:
138+
- name: Check out repository
139+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
140+
141+
- name: Set up QEMU
142+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
143+
144+
- name: Set up Docker Buildx
145+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
146+
147+
- name: Build smoke-test image
148+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
149+
with:
150+
context: .
151+
load: true
152+
platforms: ${{ matrix.platform }}
153+
push: false
154+
tags: al1s:smoke-${{ matrix.arch }}
155+
build-args: AL1S_VERSION=${{ needs.prepare.outputs.version }}
156+
cache-from: type=gha,scope=smoke-${{ matrix.arch }}
157+
cache-to: type=gha,mode=max,scope=smoke-${{ matrix.arch }}
158+
159+
- name: Run image smoke test
160+
run: docker run --rm --platform ${{ matrix.platform }} al1s:smoke-${{ matrix.arch }} python scripts/container_healthcheck.py --smoke
161+
162+
publish:
163+
name: Publish multi-platform image
164+
needs:
165+
- prepare
166+
- smoke
167+
if: needs.prepare.outputs.publish == 'true'
168+
runs-on: ubuntu-24.04
169+
timeout-minutes: 120
170+
permissions:
171+
contents: read
172+
packages: write
173+
steps:
174+
- name: Check out repository
175+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
176+
177+
- name: Set up QEMU
178+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
179+
180+
- name: Set up Docker Buildx
181+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
182+
183+
- name: Log in to GHCR
184+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
185+
with:
186+
registry: ghcr.io
187+
username: ${{ github.actor }}
188+
password: ${{ secrets.GITHUB_TOKEN }}
189+
190+
- name: Generate image metadata
191+
id: metadata
192+
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
193+
with:
194+
images: ghcr.io/scu-maker-org/al1s
195+
tags: |
196+
type=raw,value=${{ needs.prepare.outputs.version }}
197+
type=raw,value=${{ needs.prepare.outputs.series }}
198+
type=raw,value=latest,enable=${{ needs.prepare.outputs.publish_latest }}
199+
200+
- name: Build and publish image
201+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
202+
with:
203+
context: .
204+
platforms: linux/amd64,linux/arm64
205+
push: true
206+
tags: ${{ steps.metadata.outputs.tags }}
207+
labels: ${{ steps.metadata.outputs.labels }}
208+
build-args: AL1S_VERSION=${{ needs.prepare.outputs.version }}
209+
cache-from: |
210+
type=gha,scope=smoke-amd64
211+
type=gha,scope=smoke-arm64
212+
cache-to: type=gha,mode=max,scope=publish-multiarch
213+
provenance: mode=max
214+
sbom: true

0 commit comments

Comments
 (0)