Repository navigation
199 lines (177 loc) · 7.62 KB
/
Copy pathdocker.yml
File metadata and controls
199 lines (177 loc) · 7.62 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
name: Docker
# The image is only useful if it is known-good, and "it built" is a weak
# signal: the two bugs this pipeline caught during bring-up (a venv whose
# console-script shebang pointed at a builder-only path, and an editable
# install whose .pth dangled after the stage copy) both produced an image that
# built cleanly and died on first exec. So CI builds *and* drives it over real
# MCP stdio before publishing anything.
on:
push:
branches: [ main ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-verify:
runs-on: ubuntu-latest
permissions:
contents: read
# Needed only to push; the PR path never reaches the push step.
packages: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
# The action caches its own buildx binary in the Actions cache by
# default. On a workflow that also publishes release images that is a
# supply-chain path -- a poisoned cache entry could substitute the
# builder itself. Downloading it fresh costs seconds.
cache-binary: false
- name: Build image (load into the local daemon)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
# Single-arch here so the image lands in the daemon and can actually
# be run. Multi-arch cannot be --load'ed; that build is a separate
# job below, gated on this one passing.
load: true
tags: uxarray-mcp:ci
# Read the shared cache but never write it. A job that both consumes
# a mutable cache and produces artifacts others trust is the
# cache-poisoning shape zizmor flags: a compromised PR run could seed
# a layer that a later release build silently reuses. Writes happen
# only from the warm-cache job below, which runs on main and
# publishes nothing.
cache-from: type=gha
- name: Report image size
run: |
docker images uxarray-mcp:ci --format 'image size: {{.Size}}'
- name: Verify fixtures match the manifest
run: |
docker run --rm -i --entrypoint python uxarray-mcp:ci - --verify \
< scripts/generate_container_fixtures.py
- name: Assert the HPC stack is absent
# The image is local-only by design. If a dependency edit ever pulls
# globus-compute-sdk back in, a sealed container silently regains the
# ability to submit remote work -- fail loudly instead.
run: |
docker run --rm --entrypoint python uxarray-mcp:ci -c "
import importlib.util as u, sys
leaked = [m for m in ('globus_compute_sdk', 'academy') if u.find_spec(m)]
if leaked:
sys.exit(f'HPC dependencies leaked into the image: {leaked}')
print('confirmed local-only: no globus_compute_sdk, no academy')
"
- name: Assert the server does not run as root
run: |
uid=$(docker run --rm --entrypoint id uxarray-mcp:ci -u)
test "$uid" != "0" || { echo "image runs as root"; exit 1; }
echo "runs as uid $uid"
- name: Smoke test over MCP stdio
# The real check: handshake, tool surface, and one numerical result
# compared against an analytic ground truth (total area == 4*pi).
run: python3 scripts/container_smoke_test.py --image uxarray-mcp:ci
- name: Verify the HTTP transport serves MCP
run: |
docker run -d --rm --name uxarray-http -p 8001:8001 uxarray-mcp:ci \
serve --transport http --host 0.0.0.0
for i in $(seq 1 30); do
if curl -sf -o /dev/null \
-X POST http://localhost:8001/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}'
then
echo "HTTP transport responded after ${i}s"
docker stop uxarray-http
exit 0
fi
sleep 1
done
echo "HTTP transport never became ready"
docker logs uxarray-http
docker stop uxarray-http
exit 1
publish:
# Only after every check above passes, and never from a pull request --
# a fork PR must not be able to publish to the org's registry.
needs: build-and-verify
if: github.event_name == 'push' && github.repository == 'UXARRAY/uxarray-mcp-server'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
cache-binary: false
- name: Log in to ghcr.io
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Derive tags
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push (multi-arch)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
# arm64 matters: healpix has no wheel there and builds from source,
# which is the platform most likely to break first.
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# No cache on the publishing build, by design. Released images are
# built from source every time so a poisoned or merely stale cache
# entry can never end up inside something users pull. The build is
# a few minutes; a tampered release is forever.
no-cache: true
provenance: true
sbom: true
warm-cache:
# Writes the layer cache that build-and-verify reads. Split out so that the
# only job with cache-write access runs on main, publishes nothing, and is
# never triggered by a pull request from a fork.
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
cache-binary: false
- name: Populate the build cache
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max