-
Notifications
You must be signed in to change notification settings - Fork 14
180 lines (163 loc) · 7.84 KB
/
Copy pathdeploy-image.yml
File metadata and controls
180 lines (163 loc) · 7.84 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
name: Docker image — build, test, publish
# Single source of truth for ShapePipe's environment is the Dockerfile
# (slim Python + apt system deps + uv-frozen wheels). This workflow builds
# that image, runs the test suite *inside it* — so CI tests exactly what
# ships — and publishes to ghcr.
#
# pull_request → build + test, no publish (covers fork PRs, which have no
# registry token)
# push (any branch) → build + test + publish, tagged with the branch name
# (e.g. :develop, :my-feature, and the -runtime variants)
#
# Publishing on every branch push — not just the integration branches — means
# any open PR has a pullable image (`apptainer pull …:<branch>-runtime`) that
# can be tested on a real cluster *before* merge. Same-repo branch pushes always
# carry a registry-write token, so this is safe; fork PRs still only build+test.
on:
push:
branches:
- '**'
pull_request:
branches:
- develop
- main
- master
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-test-publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
with:
driver-opts: network=host
# Two parallel tag sets. `dev` is the default (no suffix, e.g. `:latest`,
# `:develop`); `runtime` carries a `-runtime` suffix.
- name: Tags — dev (default)
id: meta-dev
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Tags — runtime
id: meta-runtime
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
suffix=-runtime,onlatest=true
# ----------------------------------------------------------------
# Build + test (every event)
# ----------------------------------------------------------------
# Build runtime first (smaller, used to smoke-test pipeline binaries).
# cache-to mode=min (not max): only the layers that survive into the
# final image are exported. The expensive base stage (apt + source-built
# OpenMPI) IS in the final image, so it still gets cached and restored —
# what min drops is the redundant re-export of intermediate-stage-only
# layers, which was ~70 s of "writing layer" on every build.
- name: Build runtime (load)
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
target: runtime
load: true
tags: ${{ steps.meta-runtime.outputs.tags }}
labels: ${{ steps.meta-runtime.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=min
# Smoke-test the binaries baked into the runtime image. Catches the
# class of regression where the image builds but a runtime tool
# (sextractor, weightwatcher) is missing or unrunnable.
- name: Test runtime — binaries
run: |
IMAGE=$(echo "${{ steps.meta-runtime.outputs.tags }}" | head -n1)
docker run --rm "$IMAGE" source-extractor --version
docker run --rm "$IMAGE" weightwatcher --version
docker run --rm "$IMAGE" psfex --version
- name: Test runtime — shapepipe entry point (read-only fs)
run: |
IMAGE=$(echo "${{ steps.meta-runtime.outputs.tags }}" | head -n1)
# --read-only + tmpfs /tmp emulates apptainer/SIF semantics: only
# /tmp is writable. shapepipe_run_example wraps shapepipe_run so
# the example tree gets copied into a mktemp workdir before running.
docker run --rm --read-only --tmpfs /tmp:rw "$IMAGE" shapepipe_run_example
# Build dev (reuses cached `base` layer; see mode=min note above)
- name: Build dev (load)
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
target: dev
load: true
tags: ${{ steps.meta-dev.outputs.tags }}
labels: ${{ steps.meta-dev.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=min
# Verify the dev-only additions are present and runnable.
- name: Test dev — interactive tools
run: |
IMAGE=$(echo "${{ steps.meta-dev.outputs.tags }}" | head -n1)
docker run --rm "$IMAGE" vim --version | head -n1
docker run --rm "$IMAGE" rg --version | head -n1
# Fast science guardrails first (tests/science) — the tier of controlled
# simulations with a known answer (e.g. the m-bias check: inject g1=0.02,
# recover after the metacal response, assert |m| < 5e-3). These run in
# seconds and gate scientific correctness, so we fire them as their own
# named check *before* the full suite: a regression that breaks the
# response correction or deconvolution fails here in seconds, with a
# clear signal, instead of being buried minutes deep in the full run.
# `-m "not slow"` keeps this the fast lane (the science tier holds only
# fast tests today; the filter makes the intent explicit and future-proof).
- name: Test dev — science guardrails (fast)
run: |
IMAGE=$(echo "${{ steps.meta-dev.outputs.tags }}" | head -n1)
docker run --rm -e HYPOTHESIS_PROFILE=ci "$IMAGE" \
pytest -rX -m "not slow" --no-cov tests/science
# The actual test suite, run inside the shipped image — replacing the
# retired conda-based suite. pytest exercises the same wheels, binaries,
# and Python (3.12) that production runs on, not a parallel environment.
# pyproject's addopts add `--cov=shapepipe`; COVERAGE_FILE is set to /tmp
# in the image so it works on read-only filesystems too. The Hypothesis
# profile is explicit here so CI always uses the deterministic, capped
# property-test profile even if the default changes for local exploration.
- name: Test dev — pytest suite
run: |
IMAGE=$(echo "${{ steps.meta-dev.outputs.tags }}" | head -n1)
docker run --rm -e HYPOTHESIS_PROFILE=ci -e SHAPEPIPE_ON_CANDIDE=0 "$IMAGE" pytest -rX
# ----------------------------------------------------------------
# Publish (push events only — never on pull_request, incl. forks).
# Fires on any branch; the image is tagged with the branch name.
# ----------------------------------------------------------------
- name: Log in to the Container registry
if: github.event_name == 'push'
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push runtime
if: github.event_name == 'push'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
target: runtime
push: true
tags: ${{ steps.meta-runtime.outputs.tags }}
labels: ${{ steps.meta-runtime.outputs.labels }}
cache-from: type=gha
- name: Push dev
if: github.event_name == 'push'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
target: dev
push: true
tags: ${{ steps.meta-dev.outputs.tags }}
labels: ${{ steps.meta-dev.outputs.labels }}
cache-from: type=gha