1- name : Lint, Test, Build, and Publish Docker Images
1+ name : Lint, Release & Publish
22
33on :
44 push :
77 branches : [main, master]
88
99permissions :
10- contents : write # semantic-release & auto-formatter commits
11- packages : write # push to Docker Hub / GHCR
10+ contents : write
1211
1312jobs :
1413 # ──────────────────────────────────────────────────────────────────────────
15- # 🧹 Lint
14+ # Lint & Test
1615 # ──────────────────────────────────────────────────────────────────────────
1716 lint :
18- name : " 🧹 Lint Code & Dockerfiles "
17+ name : Lint, Test & Validate Compose Files
1918 runs-on : ubuntu-latest
20-
2119 steps :
22- - name : " 🕺 Checkout repository"
23- uses : actions/checkout@v5
24- with :
25- token : ${{ secrets.GITHUB_TOKEN }}
20+ - name : Checkout repository
21+ uses : actions/checkout@v4
2622
27- - name : " 🐍 Set up Python"
23+ - name : Set up Python
2824 uses : actions/setup-python@v5
2925 with :
30- python-version : " 3.11"
26+ python-version : ' 3.11'
3127
32- - name : " 📦 Cache pip dependencies"
33- uses : actions/cache@v5
28+ - name : Cache pip dependencies
29+ uses : actions/cache@v4
3430 with :
3531 path : ~/.cache/pip
36- key : ${{ runner.os }}-pip-lint-${{ hashFiles('**/*_reqs_* .txt', '**/ pyproject.toml') }}
32+ key : ${{ runner.os }}-pip-lint-${{ hashFiles('requirements .txt', 'pyproject.toml') }}
3733 restore-keys : |
3834 ${{ runner.os }}-pip-lint-
3935
40- - name : " ⚙️ Install Linting Tools "
36+ - name : Install tools and project dependencies
4137 run : |
42- python -m pip install --upgrade pip
43- pip install "ruff==0.4.0" black
44- sudo wget -qO /usr/local/bin/hadolint \
45- https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
46- sudo chmod +x /usr/local/bin/hadolint
38+ pip install --upgrade pip
39+ pip install -r requirements.txt
40+ pip install -e .
41+ pip install ruff==0.4.0 black pytest
4742
48- - name : " ✨ Run Ruff Linter"
49- run : |
50- if [ "${{ github.event_name }}" = "pull_request" ]; then
51- ruff check . --output-format=github
52- else
53- ruff check . --fix --output-format=github || true
54- fi
43+ - name : Run Ruff Linter
44+ run : ruff check . --output-format=github
5545
56- - name : " ⚫️ Run Black Formatter"
57- run : |
58- if [ "${{ github.event_name }}" = "pull_request" ]; then
59- black --check .
60- else
61- black .
62- git diff --exit-code || true
63- fi
64-
65- - name : " 🐳 Lint Dockerfiles"
46+ - name : Run Black Formatter Check
47+ run : black --check .
48+
49+ - name : Run Tests
50+ run : pytest tests/ --tb=short -q
51+
52+ - name : Validate Compose Files
6653 run : |
67- hadolint docker/api/Dockerfile || true
68- hadolint docker/sandbox/Dockerfile || true
54+ docker compose -f docker-compose.yml config --quiet --no-interpolate
55+ docker compose -f docker-compose.yml -f docker-compose.gpu.yml config --quiet --no-interpolate
6956
7057 # ──────────────────────────────────────────────────────────────────────────
71- # ✅ Tests
58+ # Release — semantic versioning, changelog, and GitHub release
59+ # Uses cycjimmy/semantic-release-action which correctly exposes step outputs
60+ # so the publish job can gate on whether a release was actually cut.
7261 # ──────────────────────────────────────────────────────────────────────────
73- test :
74- name : " ✅ Run Unit Tests "
62+ release :
63+ name : Git Version & Changelog
7564 runs-on : ubuntu-latest
7665 needs : lint
77- strategy :
78- fail-fast : false
79- matrix :
80- python-version : ["3.11", "3.12"]
81-
66+ if : github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
67+ outputs :
68+ new_release_published : ${{ steps.semantic.outputs.new_release_published }}
69+ new_release_version : ${{ steps.semantic.outputs.new_release_version }}
8270 steps :
83- - name : " 🕺 Checkout repository"
84- uses : actions/checkout@v5
71+ - name : Checkout repository with full history
72+ uses : actions/checkout@v4
8573 with :
74+ fetch-depth : 0
75+ persist-credentials : true
8676 token : ${{ secrets.GITHUB_TOKEN }}
8777
88- - name : " 🐍 Set up Python ${{ matrix.python-version }} "
78+ - name : Set up Python (needed for prepareCmd in .releaserc.json)
8979 uses : actions/setup-python@v5
9080 with :
91- python-version : ${{ matrix.python-version }}
81+ python-version : ' 3.11 '
9282
93- - name : " 📦 Cache pip dependencies"
94- uses : actions/cache@v5
83+ - name : Run semantic-release
84+ id : semantic
85+ uses : cycjimmy/semantic-release-action@v4
9586 with :
96- path : ~/.cache/pip
97- key : ${{ runner.os }}-pip-test-${{ matrix.python-version }}-${{ hashFiles('**/*_reqs_*.txt', '**/pyproject.toml') }}
98- restore-keys : |
99- ${{ runner.os }}-pip-test-${{ matrix.python-version }}-
87+ extra_plugins : |
88+ @semantic-release/commit-analyzer
89+ @semantic-release/release-notes-generator
90+ @semantic-release/changelog
91+ @semantic-release/exec
92+ @semantic-release/git
93+ @semantic-release/github
94+ env :
95+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
10096
101- - name : " ⚙️ Install Project & Test Deps"
97+ - name : Report release outcome
98+ if : steps.semantic.outputs.new_release_published == 'true'
10299 run : |
103- python -m pip install --upgrade pip
104- pip install -r api_unhashed_reqs.txt
105- pip install --require-hashes -r api_reqs_hashed.txt
106- pip install -r sandbox_reqs_unhashed.txt
107- pip install --require-hashes -r sandbox_reqs_hashed.txt
108- pip install pytest pytest-cov
109-
110- - name : " ✅ Run Pytest with Coverage"
111- run : pytest tests/ --cov=src --cov-report=term-missing
100+ echo "New release: v${{ steps.semantic.outputs.new_release_version }}"
112101
113102 # ──────────────────────────────────────────────────────────────────────────
114- # 🚀 Build & Publish Docker images
103+ # Publish — build wheel and push to PyPI
104+ # Only runs when semantic-release actually cut a new release
115105 # ──────────────────────────────────────────────────────────────────────────
116- build_and_publish :
117- name : " 🚀 Build, Tag, and Publish Images to Docker Hub "
106+ publish :
107+ name : Build & Publish to PyPI
118108 runs-on : ubuntu-latest
119- needs : test
120- if : github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
109+ needs : release
110+ if : needs.release.outputs.new_release_published == 'true'
121111
122112 steps :
123- - name : " 🕺 Checkout repository"
124- uses : actions/checkout@v5
113+ - name : Checkout repository at new release tag
114+ uses : actions/checkout@v4
125115 with :
126- fetch-depth : 0
127- persist-credentials : true
128- token : ${{ secrets.GITHUB_TOKEN }}
129-
130- - name : " ⚙️ Setup QEMU"
131- uses : docker/setup-qemu-action@v3
132-
133- - name : " ⚙️ Setup Docker Buildx"
134- uses : docker/setup-buildx-action@v3
116+ # Check out the bumped tag so pyproject.toml already has the new version
117+ ref : v${{ needs.release.outputs.new_release_version }}
135118
136- - name : " 🔑 Login to Docker Hub "
137- uses : docker/login-action@v3
119+ - name : Set up Python
120+ uses : actions/setup-python@v5
138121 with :
139- username : thanosprime
140- password : ${{ secrets.DOCKERHUB_THANOSPRIME }}
122+ python-version : ' 3.11'
141123
142- # quick cleanup of any dangling layers on the runner
143- - name : " 🧼 Prune Docker System Cache"
144- run : docker system prune -af || true
124+ - name : Install build tools
125+ run : |
126+ pip install --upgrade pip
127+ pip install build twine
145128
146- # ----- semantic-release -------------------------------------------------
147- - name : " 🚀 Setup Node.js"
148- uses : actions/setup-node@v4
149- with :
150- node-version : " 20"
129+ - name : Build wheel and source distribution
130+ run : python -m build
151131
152- - name : " ⚙️ Install semantic-release"
153- run : |
154- npm install -g semantic-release \
155- @semantic-release/commit-analyzer \
156- @semantic-release/release-notes-generator \
157- @semantic-release/changelog \
158- @semantic-release/exec \
159- @semantic-release/git \
160- @semantic-release/github
132+ - name : Verify distribution contents
133+ run : twine check dist/*
161134
162- - name : " 🚀 Run semantic-release"
163- id : semantic
135+ - name : Publish to PyPI
164136 env :
165- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
166- run : npx semantic-release
167-
168- - name : " 🏷️ Extract Git Tag Version"
169- id : get_version
170- run : |
171- git fetch --tags origin
172- VERSION=$(git describe --tags --exact-match HEAD 2>/dev/null || git describe --tags --abbrev=0 HEAD 2>/dev/null)
173- if [ -z "$VERSION" ]; then
174- echo "::error::Could not determine version tag after semantic-release."
175- exit 1
176- fi
177- VERSION=${VERSION#v}
178- echo "Detected version: $VERSION"
179- echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
180-
181- # ---------------- API image metadata ----------------
182- - name : " 🔧 Define API Image Metadata"
183- id : meta_api
184- uses : docker/metadata-action@v5
185- with :
186- images : thanosprime/entities-api-api
187- tags : |
188- type=semver,pattern={{version}},value=${{ steps.get_version.outputs.VERSION }}
189- type=raw,value=latest,enable={{is_default_branch}}
190- type=sha,prefix=sha-
191-
192- # ---------------- Sandbox image metadata ------------
193- - name : " 🔧 Define Sandbox Image Metadata"
194- id : meta_sandbox
195- uses : docker/metadata-action@v5
196- with :
197- images : thanosprime/entities-api-sandbox
198- tags : |
199- type=semver,pattern={{version}},value=${{ steps.get_version.outputs.VERSION }}
200- type=raw,value=latest,enable={{is_default_branch}}
201- type=sha,prefix=sha-
202-
203- # ---------------- Build / Push API ------------------
204- - name : " 🏗️ Build & Push API Image"
205- uses : docker/build-push-action@v5
206- with :
207- context : .
208- file : ./docker/api/Dockerfile
209- push : true
210- tags : ${{ steps.meta_api.outputs.tags }}
211- labels : ${{ steps.meta_api.outputs.labels }}
212- platforms : linux/amd64
213- cache-from : type=gha
214- # no cache-to: export disabled
215-
216- # ---------------- Build / Push Sandbox --------------
217- - name : " 🏗️ Build & Push Sandbox Image"
218- uses : docker/build-push-action@v5
219- with :
220- context : .
221- file : ./docker/sandbox/Dockerfile
222- push : true
223- tags : ${{ steps.meta_sandbox.outputs.tags }}
224- labels : ${{ steps.meta_sandbox.outputs.labels }}
225- platforms : linux/amd64
226- cache-from : type=gha
227- # no cache-to: export disabled
137+ TWINE_USERNAME : __token__
138+ TWINE_PASSWORD : ${{ secrets.PYPI_API_TOKEN }}
139+ run : twine upload dist/*
0 commit comments