-
Notifications
You must be signed in to change notification settings - Fork 0
203 lines (165 loc) · 5.3 KB
/
Copy pathci.yml
File metadata and controls
203 lines (165 loc) · 5.3 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
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.13"
NODE_VERSION: "22"
jobs:
backend-tests:
name: Backend tests and coverage
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: "0.11.29"
enable-cache: true
cache-dependency-glob: backend/uv.lock
- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Run the test suite under coverage
run: uv run coverage run -m unittest discover -s tests -v
- name: Enforce the coverage gate
run: uv run coverage report
- name: Publish the coverage report
if: always()
run: uv run coverage xml -o coverage.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: backend-coverage
path: backend/coverage.xml
retention-days: 7
authorization-tests:
name: Authorization and file-access boundaries
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: "0.11.29"
enable-cache: true
cache-dependency-glob: backend/uv.lock
- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: uv sync --frozen
- name: Role boundaries and tenant isolation
run: uv run python -m unittest tests.test_authorization -v
- name: Protected file and export access
run: uv run python -m unittest tests.test_file_access -v
- name: Secret handling
run: uv run python -m unittest tests.test_config -v
secret-scan:
name: No committed secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Reject fallback values for secret environment variables
run: |
if grep -rnE 'os\.getenv\(\s*"[^"]*(SECRET|PASSWORD)[^"]*"\s*,\s*"[^"]+"' \
--include='*.py' --exclude-dir=.venv --exclude-dir=tests backend; then
echo "::error::A secret has a hard-coded fallback. Read it from the environment and fail closed instead."
exit 1
fi
- name: Reject committed environment files
run: |
if git ls-files --error-unmatch .env backend/.env frontend/.env 2>/dev/null; then
echo "::error::An .env file is tracked in git."
exit 1
fi
frontend:
name: Frontend lint and build
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Unit tests with the coverage gate
run: npm run test:coverage
- name: Build
run: npm run build
- name: Audit runtime dependencies
run: npm audit --omit=dev --audit-level=high
- uses: actions/upload-artifact@v4
if: always()
with:
name: frontend-coverage
path: frontend/coverage/lcov.info
retention-days: 7
shell:
name: Shell scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate syntax
run: bash -n run.sh scripts/api-smoke.sh
- name: ShellCheck
run: shellcheck --severity=warning run.sh scripts/api-smoke.sh
docker:
name: Container image
runs-on: ubuntu-latest
needs: [backend-tests, frontend]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build the image
uses: docker/build-push-action@v6
with:
context: .
push: false
load: true
tags: placement-portal:ci
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Boot the container and check the health endpoint
env:
APP_ENV: production
run: |
docker run -d --name portal \
-e APP_ENV=production \
-e FLASK_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" \
-e JWT_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" \
-p 5001:5001 placement-portal:ci
for attempt in $(seq 1 30); do
if curl -fsS http://localhost:5001/api/health | grep -q '"ok"'; then
echo "Health check passed on attempt $attempt"
exit 0
fi
sleep 2
done
echo "::error::The container never became healthy."
docker logs portal
exit 1
- name: Container logs
if: always()
run: docker logs portal || true