refactor(pixel-scale): derive from image WCS instead of hardcoding it #576
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |