-
Notifications
You must be signed in to change notification settings - Fork 1
340 lines (306 loc) · 11.7 KB
/
Copy pathci.yml
File metadata and controls
340 lines (306 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
name: CI
# Triggers:
# - on every PR / master push: backend pytest, frontend typecheck +
# lint, docker-compose syntax check. NO image build, NO ghcr push.
# - on push of a semver tag (``v*``): the same tests, then build +
# push all three images (backend / frontend / local-ai) to ghcr
# with the matching semver tag matrix and create a GitHub Release
# with auto-generated notes.
#
# Rationale for tag-only publishing: keeps the package registry clean
# (no accumulating ``:sha-XXX`` versions for every merge into master),
# matches the team's intent that ``:latest`` always points at a
# deliberate, named release.
on:
push:
branches: [main, master]
# Semver tags (``v0.2.0``, ``v0.3.0-rc1``, …) trigger a release run:
# builds the image with semver-derived tags AND creates a GitHub
# Release with auto-generated notes from commits since the previous
# tag. Pre-releases are detected by the ``-`` suffix and don't move
# the ``:latest`` image tag.
tags: ["v*"]
pull_request:
branches: [main, master]
# Cancel an in-flight run when a new commit comes in on the same branch
# / PR — saves Actions minutes on rapid pushes.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
# ghcr.io requires lowercase image names; ${{ github.repository_owner }}
# preserves case, so we lowercase it at job time via a setup step.
BACKEND_IMAGE_NAME: wow-ai-log-analyzer-backend
FRONTEND_IMAGE_NAME: wow-ai-log-analyzer-frontend
LOCAL_AI_IMAGE_NAME: wow-ai-log-analyzer-local-ai
jobs:
backend-tests:
name: Backend (pytest)
runs-on: ubuntu-latest
# Tests run against the same engine as production (PostgreSQL),
# so dialect-specific features (JSONB ops, native UUID, …) are
# actually exercised. Redis is needed for the rate-limiter that
# runs on auth endpoints.
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U test -d test"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 5
env:
# GitHub-hosted services are exposed on localhost via the port
# mappings above. conftest reads TEST_DATABASE_URL first, then
# falls back to DATABASE_URL, then SQLite.
TEST_DATABASE_URL: postgresql+asyncpg://test:test@127.0.0.1:5432/test
REDIS_HOST: 127.0.0.1
REDIS_PORT: "6379"
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: latest
enable-cache: true
cache-dependency-glob: backend/uv.lock
- name: Sync dependencies
working-directory: backend
# ``--frozen`` requires backend/uv.lock to be committed (which
# it is); CI fails loudly if the lock and pyproject diverge.
run: uv sync --frozen
- name: Run pytest
working-directory: backend
run: uv run pytest -q
frontend-checks:
name: Frontend (typecheck + lint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
# ``npm ci`` requires package-lock.json (recommended for
# reproducibility). Falls back to ``npm install`` for the very
# first push before a lock has been committed.
run: |
if [ -f package-lock.json ]; then
npm ci
else
echo "::warning::package-lock.json not committed — using npm install. Run 'npm install' locally and commit the lockfile for reproducible builds."
npm install --no-audit --no-fund
fi
- name: TypeScript
working-directory: frontend
run: npm run typecheck
- name: ESLint
working-directory: frontend
run: npm run lint
compose-validate:
name: docker-compose syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate compose file
# ``docker compose config`` substitutes env vars; we provide
# the example values so required-ish placeholders don't fail
# the parse step.
run: |
cp .env.example .env
docker compose config --quiet
# Three parallel image jobs (backend + frontend + local-ai). All
# deployment-agnostic since the runtime-config refactor (see
# docs/SETUP.md §8).
#
# Builds are gated on ``refs/tags/v*`` ONLY — pushing to master no
# longer creates ghcr package versions. Tests + lint still run on
# every push to master so regressions are caught early; images are
# only published when a deliberate semver tag is created. This
# prevents the registry from accumulating throwaway ``:sha-XXX``
# versions for every merge into master.
build-backend:
name: Build + push backend → ghcr.io
needs: [backend-tests, frontend-checks, compose-validate]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Lowercase repository owner
id: lowercase
run: |
echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
- name: Log in to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute image tags
# Tag matrix (this job only runs on semver tag pushes):
#
# tag v0.2.0 → :0.2.0, :0.2, :0, :latest (stable)
# tag v0.2.0-rc1 → :0.2.0-rc1 (pre-release)
#
# ``flavor.latest=auto`` decides whether to add ``:latest`` —
# yes for plain semver, no for pre-releases.
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.lowercase.outputs.owner }}/${{ env.BACKEND_IMAGE_NAME }}
flavor: |
latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build & push backend
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# ``scope`` keeps backend/frontend cache namespaces separate
# so they don't evict each other from the 10 GB GHA cache.
cache-from: type=gha,scope=backend
cache-to: type=gha,mode=max,scope=backend
build-frontend:
name: Build + push frontend → ghcr.io
needs: [backend-tests, frontend-checks, compose-validate]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Lowercase repository owner
id: lowercase
run: |
echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
- name: Log in to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute image tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.lowercase.outputs.owner }}/${{ env.FRONTEND_IMAGE_NAME }}
flavor: |
latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build & push frontend
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend
build-local-ai:
name: Build + push local-ai → ghcr.io
needs: [backend-tests, frontend-checks, compose-validate]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Lowercase repository owner
id: lowercase
run: |
echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
- name: Log in to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute image tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.lowercase.outputs.owner }}/${{ env.LOCAL_AI_IMAGE_NAME }}
flavor: |
latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build & push local-ai
uses: docker/build-push-action@v5
with:
context: ./local-ai
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Image is mostly the upstream llama.cpp:server-cuda base
# (~2.3 GB compressed). ghcr deduplicates layers, so only
# the thin supervisor.py layer is uploaded after the first
# push. Cache scope keeps the upstream base layers warm
# across runs.
cache-from: type=gha,scope=local-ai
cache-to: type=gha,mode=max,scope=local-ai
release:
name: Create GitHub Release
needs: [build-backend, build-frontend, build-local-ai]
# Only on tag pushes — main-branch pushes don't need a release entry.
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
# Full history so the release-notes generator can diff
# against the previous tag.
fetch-depth: 0
- name: Create release
uses: softprops/action-gh-release@v2
with:
# Auto-generate the release body from commits + merged PRs
# between the previous tag and this one (GitHub's standard
# changelog format with PRs/contributors grouped).
generate_release_notes: true
# Pre-release suffix (``-rc1``, ``-beta``, …) → mark as
# pre-release so users who pin :latest don't accidentally
# ship it. Stable semver tags become normal releases.
prerelease: ${{ contains(github.ref_name, '-') }}