Skip to content

Commit 7094c5e

Browse files
authored
FEAT local Docker setup for running GUI or Jupyter (microsoft#1357)
1 parent 9a2e036 commit 7094c5e

14 files changed

Lines changed: 1085 additions & 79 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ RUN apt-get update && apt-get install -y \
2323
&& apt-get clean && rm -rf /var/lib/apt/lists/*
2424

2525
# Install the Azure CLI, Microsoft ODBC Driver 18 & SQL tools
26+
# Note: Debian Trixie's sqv rejects SHA1 signatures, so we use gpg directly to import the Microsoft key
2627
RUN apt-get update && apt-get install -y \
2728
apt-transport-https \
2829
ca-certificates \
2930
gnupg \
3031
lsb-release \
3132
&& curl -sL https://packages.microsoft.com/keys/microsoft.asc \
32-
| gpg --dearmor \
33-
> /usr/share/keyrings/microsoft-archive-keyring.gpg \
33+
| gpg --dearmor \
34+
> /usr/share/keyrings/microsoft-archive-keyring.gpg \
3435
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" \
35-
> /etc/apt/sources.list.d/microsoft.list \
36+
> /etc/apt/sources.list.d/microsoft.list \
3637
&& apt-get update \
3738
&& ACCEPT_EULA=Y apt-get install -y \
3839
msodbcsql18 \

.github/workflows/docker_build.yml

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# Tests Docker image builds for devcontainer and production
2+
3+
name: docker_build
4+
5+
on:
6+
push:
7+
branches:
8+
- "main"
9+
pull_request:
10+
branches:
11+
- "main"
12+
- "release/**"
13+
workflow_dispatch:
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
# Stage 1: Build devcontainer base image
21+
build-devcontainer:
22+
name: Build Devcontainer
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: read
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Set up Docker Buildx
30+
uses: docker/setup-buildx-action@v3
31+
32+
- name: Build devcontainer image
33+
uses: docker/build-push-action@v5
34+
with:
35+
context: .devcontainer
36+
file: .devcontainer/Dockerfile
37+
push: false
38+
tags: pyrit-devcontainer:latest
39+
load: true
40+
cache-from: type=gha
41+
cache-to: type=gha,mode=max
42+
43+
- name: Save devcontainer image
44+
run: docker save pyrit-devcontainer:latest | gzip > devcontainer.tar.gz
45+
46+
- name: Upload devcontainer artifact
47+
uses: actions/upload-artifact@v4
48+
with:
49+
name: devcontainer-image
50+
path: devcontainer.tar.gz
51+
retention-days: 1
52+
53+
# Stage 2: Build production images (parallel)
54+
build-production-local:
55+
name: Build Production (local)
56+
runs-on: ubuntu-latest
57+
needs: build-devcontainer
58+
permissions:
59+
contents: read
60+
steps:
61+
- uses: actions/checkout@v4
62+
63+
- name: Download devcontainer image
64+
uses: actions/download-artifact@v4
65+
with:
66+
name: devcontainer-image
67+
68+
- name: Load devcontainer image
69+
run: gunzip -c devcontainer.tar.gz | docker load
70+
71+
- name: Set up Docker Buildx
72+
uses: docker/setup-buildx-action@v3
73+
with:
74+
driver: docker
75+
76+
- name: Build production image (local)
77+
uses: docker/build-push-action@v5
78+
with:
79+
context: .
80+
file: docker/Dockerfile
81+
push: false
82+
tags: pyrit:local-test
83+
load: true
84+
build-args: |
85+
BASE_IMAGE=pyrit-devcontainer:latest
86+
PYRIT_SOURCE=local
87+
88+
- name: Save production image
89+
run: docker save pyrit:local-test | gzip > local.tar.gz
90+
91+
- name: Upload production artifact
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: production-local-image
95+
path: local.tar.gz
96+
retention-days: 1
97+
98+
build-production-pypi:
99+
name: Build Production (PyPI)
100+
runs-on: ubuntu-latest
101+
needs: build-devcontainer
102+
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
103+
permissions:
104+
contents: read
105+
steps:
106+
- uses: actions/checkout@v4
107+
108+
- name: Get latest PyRIT version from PyPI
109+
id: pypi-version
110+
run: |
111+
VERSION=$(pip index versions pyrit 2>/dev/null | head -1 | grep -oP '\(\K[^)]+' || echo "0.10.0")
112+
echo "version=$VERSION" >> $GITHUB_OUTPUT
113+
echo "Latest PyRIT version on PyPI: $VERSION"
114+
115+
- name: Download devcontainer image
116+
uses: actions/download-artifact@v4
117+
with:
118+
name: devcontainer-image
119+
120+
- name: Load devcontainer image
121+
run: gunzip -c devcontainer.tar.gz | docker load
122+
123+
- name: Set up Docker Buildx
124+
uses: docker/setup-buildx-action@v3
125+
with:
126+
driver: docker
127+
128+
- name: Build production image (PyPI)
129+
uses: docker/build-push-action@v5
130+
with:
131+
context: .
132+
file: docker/Dockerfile
133+
push: false
134+
tags: pyrit:pypi-test
135+
load: true
136+
build-args: |
137+
BASE_IMAGE=pyrit-devcontainer:latest
138+
PYRIT_SOURCE=pypi
139+
PYRIT_VERSION=${{ steps.pypi-version.outputs.version }}
140+
141+
- name: Save production image
142+
run: docker save pyrit:pypi-test | gzip > pypi.tar.gz
143+
144+
- name: Upload production artifact
145+
uses: actions/upload-artifact@v4
146+
with:
147+
name: production-pypi-image
148+
path: pypi.tar.gz
149+
retention-days: 1
150+
151+
# Stage 3: Test production images (parallel)
152+
test-local-import:
153+
name: Test Import (local)
154+
runs-on: ubuntu-latest
155+
needs: build-production-local
156+
steps:
157+
- name: Download production image
158+
uses: actions/download-artifact@v4
159+
with:
160+
name: production-local-image
161+
162+
- name: Load production image
163+
run: gunzip -c local.tar.gz | docker load
164+
165+
- name: Test PyRIT import
166+
run: |
167+
docker run --rm --entrypoint /opt/venv/bin/python pyrit:local-test -c "import pyrit; print(f'PyRIT version: {pyrit.__version__}')"
168+
169+
test-local-gui:
170+
name: Test GUI (local)
171+
runs-on: ubuntu-latest
172+
needs: build-production-local
173+
steps:
174+
- name: Download production image
175+
uses: actions/download-artifact@v4
176+
with:
177+
name: production-local-image
178+
179+
- name: Load production image
180+
run: gunzip -c local.tar.gz | docker load
181+
182+
- name: Test GUI mode
183+
run: |
184+
docker run -d --name pyrit-gui-test -e PYRIT_MODE=gui -p 8000:8000 pyrit:local-test
185+
186+
echo "Waiting for GUI to start..."
187+
sleep 15
188+
189+
if ! docker ps | grep -q pyrit-gui-test; then
190+
echo "Container not running! Logs:"
191+
docker logs pyrit-gui-test
192+
exit 1
193+
fi
194+
195+
echo "Testing API health endpoint..."
196+
curl -sf http://localhost:8000/api/health || (echo "Health endpoint failed" && docker logs pyrit-gui-test && exit 1)
197+
198+
echo "Testing frontend is served..."
199+
RESPONSE=$(curl -s http://localhost:8000/)
200+
echo "$RESPONSE" | head -5
201+
echo "$RESPONSE" | grep -iq '<!doctype html>' || (echo "Frontend not served" && docker logs pyrit-gui-test && exit 1)
202+
203+
echo "✅ GUI mode tests passed"
204+
docker stop pyrit-gui-test && docker rm pyrit-gui-test
205+
206+
test-local-jupyter:
207+
name: Test Jupyter (local)
208+
runs-on: ubuntu-latest
209+
needs: build-production-local
210+
steps:
211+
- name: Download production image
212+
uses: actions/download-artifact@v4
213+
with:
214+
name: production-local-image
215+
216+
- name: Load production image
217+
run: gunzip -c local.tar.gz | docker load
218+
219+
- name: Test Jupyter mode
220+
run: |
221+
docker run -d --name pyrit-jupyter-test -e PYRIT_MODE=jupyter -p 8888:8888 pyrit:local-test
222+
223+
echo "Waiting for Jupyter to start..."
224+
sleep 20
225+
226+
if ! docker ps | grep -q pyrit-jupyter-test; then
227+
echo "Container not running! Logs:"
228+
docker logs pyrit-jupyter-test
229+
exit 1
230+
fi
231+
232+
echo "Testing Jupyter responds..."
233+
curl -sf http://localhost:8888/api || (echo "Jupyter API failed" && docker logs pyrit-jupyter-test && exit 1)
234+
235+
echo "✅ Jupyter mode tests passed"
236+
docker stop pyrit-jupyter-test && docker rm pyrit-jupyter-test
237+
238+
test-pypi-import:
239+
name: Test Import (PyPI)
240+
runs-on: ubuntu-latest
241+
needs: build-production-pypi
242+
steps:
243+
- name: Download production image
244+
uses: actions/download-artifact@v4
245+
with:
246+
name: production-pypi-image
247+
248+
- name: Load production image
249+
run: gunzip -c pypi.tar.gz | docker load
250+
251+
- name: Test PyRIT import
252+
run: |
253+
docker run --rm --entrypoint /opt/venv/bin/python pyrit:pypi-test -c "import pyrit; print(f'PyRIT version: {pyrit.__version__}')"
254+
255+
test-pypi-gui:
256+
name: Test GUI (PyPI)
257+
runs-on: ubuntu-latest
258+
needs: build-production-pypi
259+
steps:
260+
- name: Download production image
261+
uses: actions/download-artifact@v4
262+
with:
263+
name: production-pypi-image
264+
265+
- name: Load production image
266+
run: gunzip -c pypi.tar.gz | docker load
267+
268+
- name: Test GUI mode
269+
run: |
270+
docker run -d --name pyrit-gui-pypi -e PYRIT_MODE=gui -p 8000:8000 pyrit:pypi-test
271+
272+
echo "Waiting for GUI to start..."
273+
sleep 15
274+
275+
if ! docker ps | grep -q pyrit-gui-pypi; then
276+
echo "Container not running! Logs:"
277+
docker logs pyrit-gui-pypi
278+
exit 1
279+
fi
280+
281+
curl -sf http://localhost:8000/api/health || (echo "Health endpoint failed" && docker logs pyrit-gui-pypi && exit 1)
282+
283+
RESPONSE=$(curl -s http://localhost:8000/)
284+
echo "$RESPONSE" | head -5
285+
echo "$RESPONSE" | grep -iq '<!doctype html>' || (echo "Frontend not served" && docker logs pyrit-gui-pypi && exit 1)
286+
287+
echo "✅ GUI mode tests passed (PyPI)"
288+
docker stop pyrit-gui-pypi && docker rm pyrit-gui-pypi
289+
290+
test-pypi-jupyter:
291+
name: Test Jupyter (PyPI)
292+
runs-on: ubuntu-latest
293+
needs: build-production-pypi
294+
steps:
295+
- name: Download production image
296+
uses: actions/download-artifact@v4
297+
with:
298+
name: production-pypi-image
299+
300+
- name: Load production image
301+
run: gunzip -c pypi.tar.gz | docker load
302+
303+
- name: Test Jupyter mode
304+
run: |
305+
docker run -d --name pyrit-jupyter-pypi -e PYRIT_MODE=jupyter -p 8888:8888 pyrit:pypi-test
306+
307+
echo "Waiting for Jupyter to start..."
308+
sleep 20
309+
310+
if ! docker ps | grep -q pyrit-jupyter-pypi; then
311+
echo "Container not running! Logs:"
312+
docker logs pyrit-jupyter-pypi
313+
exit 1
314+
fi
315+
316+
curl -sf http://localhost:8888/api || (echo "Jupyter API failed" && docker logs pyrit-jupyter-pypi && exit 1)
317+
318+
echo "✅ Jupyter mode tests passed (PyPI)"
319+
docker stop pyrit-jupyter-pypi && docker rm pyrit-jupyter-pypi

0 commit comments

Comments
 (0)