Skip to content

Commit 557c179

Browse files
authored
Add multi-stage Dockerfile for canonical GMAT base image (#70)
- Multi-stage Dockerfile at repo root. Stage 1 downloads + extracts the upstream Linux tarball using the same SourceForge URL template and per-version size floors as src/download.ts. Stage 2 layers pyenv (v2.5.7 SHA) plus Python 3.10.20 / 3.11.15 / 3.12.13 on ubuntu:24.04, copies GMAT_ROOT to /opt/gmat, runs BuildApiStartupFile.py, applies OCI labels. - New .dockerignore excludes dist/, node_modules/, site/, .git/. - PYTHONPATH set absolutely as /opt/gmat/api (the issue's $GMAT_ROOT/api:$PYTHONPATH form would expand to a trailing colon on a fresh image and put CWD on sys.path). - Charter §3's PyPI convenience pre-install dropped to keep the image focused on GMAT + Python toolchain. One-line charter update at v0.3 cut. Closes #60
1 parent cb2b017 commit 557c179

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist/
2+
node_modules/
3+
site/
4+
.git/

Dockerfile

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# syntax=docker/dockerfile:1.6
2+
3+
# Canonical astro-tools/setup-gmat GMAT base image.
4+
#
5+
# Stage 1 downloads + extracts the upstream GMAT Linux tarball; stage 2 layers
6+
# pyenv-managed Pythons on top and bootstraps gmatpy. The image's contract is
7+
# the same as the Action's: GMAT installed correctly and `import gmatpy`
8+
# working in three pinned Pythons — nothing more. Downstream images layer on
9+
# top via FROM.
10+
11+
ARG GMAT_VERSION=R2026a
12+
13+
# ---------- Stage 1: download + extract GMAT ----------
14+
FROM ubuntu:24.04 AS gmat-installer
15+
16+
ARG GMAT_VERSION
17+
ARG INSTALLER_SHA256=
18+
19+
ENV DEBIAN_FRONTEND=noninteractive
20+
21+
RUN apt-get update \
22+
&& apt-get install -y --no-install-recommends \
23+
curl \
24+
ca-certificates \
25+
tar \
26+
gzip \
27+
&& rm -rf /var/lib/apt/lists/*
28+
29+
WORKDIR /tmp/installer
30+
31+
# URL template and per-version size floors mirror src/download.ts:18-22,76 — keep
32+
# them in sync. The size guard catches partial-body downloads from SourceForge
33+
# mirrors that pass HTTP-level checks; --retry-all-errors handles mid-stream
34+
# drops (vs --retry alone, which only fires on initial connection failures).
35+
RUN case "${GMAT_VERSION}" in \
36+
R2022a) min_bytes=$((360 * 1024 * 1024)) ;; \
37+
R2025a) min_bytes=$((390 * 1024 * 1024)) ;; \
38+
R2026a) min_bytes=$((380 * 1024 * 1024)) ;; \
39+
*) echo "Unsupported GMAT_VERSION: ${GMAT_VERSION}" >&2; exit 1 ;; \
40+
esac \
41+
&& url="https://sourceforge.net/projects/gmat/files/GMAT/GMAT-${GMAT_VERSION}/gmat-ubuntu-x64-${GMAT_VERSION}.tar.gz/download" \
42+
&& echo "Downloading GMAT ${GMAT_VERSION} from ${url}" \
43+
&& curl --location --fail --retry 5 --retry-all-errors \
44+
--output gmat.tar.gz "${url}" \
45+
&& size=$(stat --format=%s gmat.tar.gz) \
46+
&& if [ "${size}" -lt "${min_bytes}" ]; then \
47+
echo "Installer truncated: ${size} bytes < ${min_bytes} byte threshold" >&2; \
48+
exit 1; \
49+
fi \
50+
&& observed_sha=$(sha256sum gmat.tar.gz | awk '{print $1}') \
51+
&& echo "Installer SHA-256: ${observed_sha}" \
52+
&& if [ -n "${INSTALLER_SHA256}" ] && [ "${INSTALLER_SHA256}" != "${observed_sha}" ]; then \
53+
echo "SHA-256 mismatch: expected ${INSTALLER_SHA256}, got ${observed_sha}" >&2; \
54+
exit 1; \
55+
fi
56+
57+
# Linux tarball wraps the install in `GMAT/<version>/` — matches the resolver
58+
# in src/extract.ts:121. Verify the api/BuildApiStartupFile.py landmark before
59+
# leaving stage 1, so a layout drift fails here rather than in stage 2.
60+
RUN mkdir -p /staging \
61+
&& tar -xzf /tmp/installer/gmat.tar.gz -C /staging \
62+
&& rm /tmp/installer/gmat.tar.gz \
63+
&& test -f "/staging/GMAT/${GMAT_VERSION}/api/BuildApiStartupFile.py" \
64+
|| (echo "Expected GMAT_ROOT at /staging/GMAT/${GMAT_VERSION}; archive layout changed?" >&2 && exit 1)
65+
66+
# ---------- Stage 2: pyenv + Pythons + GMAT ----------
67+
FROM ubuntu:24.04 AS final
68+
69+
ARG GMAT_VERSION
70+
ARG INSTALLER_SHA256=
71+
ARG ACTION_VERSION=
72+
73+
ENV DEBIAN_FRONTEND=noninteractive \
74+
PYENV_ROOT=/opt/pyenv \
75+
PYTHON_310_VERSION=3.10.20 \
76+
PYTHON_311_VERSION=3.11.15 \
77+
PYTHON_312_VERSION=3.12.13
78+
79+
RUN apt-get update \
80+
&& apt-get install -y --no-install-recommends \
81+
build-essential \
82+
libssl-dev \
83+
libffi-dev \
84+
libbz2-dev \
85+
libreadline-dev \
86+
libsqlite3-dev \
87+
zlib1g-dev \
88+
liblzma-dev \
89+
tk-dev \
90+
libncursesw5-dev \
91+
xz-utils \
92+
curl \
93+
ca-certificates \
94+
git \
95+
&& rm -rf /var/lib/apt/lists/*
96+
97+
# pyenv pinned to v2.5.7. Tags on GitHub are mutable; pinning to the SHA
98+
# prevents silent retargeting if the tag is ever moved.
99+
RUN git clone --no-checkout https://github.com/pyenv/pyenv.git "${PYENV_ROOT}" \
100+
&& git -C "${PYENV_ROOT}" checkout f216b4bfb1598347137ecb3c4a8f893baf9ea37f
101+
102+
ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
103+
104+
# 3.12 listed first so `python` / `python3` resolve to the newest minor;
105+
# `python3.10` and `python3.11` shims still resolve to their own installs.
106+
RUN pyenv install "${PYTHON_310_VERSION}" \
107+
&& pyenv install "${PYTHON_311_VERSION}" \
108+
&& pyenv install "${PYTHON_312_VERSION}" \
109+
&& pyenv global \
110+
"${PYTHON_312_VERSION}" \
111+
"${PYTHON_311_VERSION}" \
112+
"${PYTHON_310_VERSION}" \
113+
&& pyenv rehash
114+
115+
COPY --from=gmat-installer /staging/GMAT/${GMAT_VERSION} /opt/gmat
116+
117+
# BuildApiStartupFile.py writes absolute paths into api/api_startup_file.txt —
118+
# must run after the GMAT root is at its final location, otherwise the file
119+
# bakes in the staging path and gmatpy import fails cryptically at runtime.
120+
RUN cd /opt/gmat && python api/BuildApiStartupFile.py
121+
122+
# A fresh image has no PYTHONPATH; the issue spec's `$GMAT_ROOT/api:$PYTHONPATH`
123+
# would expand to a trailing colon, which Python interprets as CWD on sys.path.
124+
# Set the path absolutely.
125+
ENV GMAT_ROOT=/opt/gmat \
126+
PYTHONPATH=/opt/gmat/api
127+
128+
# INSTALLER_SHA256 and ACTION_VERSION default to empty for local builds; the
129+
# publish workflow (#61) computes/passes them at build time.
130+
LABEL org.opencontainers.image.source="https://github.com/astro-tools/setup-gmat" \
131+
org.opencontainers.image.licenses="MIT" \
132+
org.opencontainers.image.title="astro-tools/gmat" \
133+
org.opencontainers.image.description="Canonical GMAT base image with gmatpy importable in pyenv-managed Python 3.10, 3.11, 3.12." \
134+
gmat.version="${GMAT_VERSION}" \
135+
gmat.source-url="https://sourceforge.net/projects/gmat/files/GMAT/GMAT-${GMAT_VERSION}/gmat-ubuntu-x64-${GMAT_VERSION}.tar.gz/download" \
136+
gmat.installer-sha256="${INSTALLER_SHA256}" \
137+
setup-gmat.version="${ACTION_VERSION}"

0 commit comments

Comments
 (0)