From a0adb7d19dc31b7c2800420a7c35a7b2efcd3c89 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sun, 28 Sep 2025 20:30:35 +0300 Subject: [PATCH 1/4] Upgrade Dockerfile Upgraded RMG from 3.7 to 3.9 Upgraded ARC from 37 to 3.12 --- Dockerfile | 318 ++++++++++++++--------------------------------------- 1 file changed, 85 insertions(+), 233 deletions(-) diff --git a/Dockerfile b/Dockerfile index 643470ad58..48be03f05f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,243 +1,95 @@ -# Stage 1: RMG setup -# RMG Dockerfile +# Stage 1: RMG setup & ARC setup # The parent image is the base image that the Dockerfile builds upon. # The RMG installation instructions suggest Anaconda for installation by source, however, we use micromamba for the Docker image due to its smaller size and less overhead. -# https://hub.docker.com/layers/mambaorg/micromamba/1.4.3-jammy/images/sha256-0c7c97be938c5522dcb9e1737bfa4499c53f6cf9e32e53897607a57ba8b148d5?context=explore -# We are using the sha256 hash to ensure that the image is not updated without our knowledge. It considered best practice to use the sha256 hash -FROM --platform=linux/amd64 mambaorg/micromamba@sha256:20fb02f2d1160265f7fabaf1601707a902ae65c6dc9e053d305441182450c368 AS rmg-stage +# Installation of ARC will also be done in this stage. +FROM --platform=linux/amd64 mambaorg/micromamba:2.2-ubuntu24.04 AS builder -# Set the user as root +# Set ARGS +ARG RMG_PY_BRANCH=main +ARG RMG_DATABASE_BRANCH=main +ARG ARC_BRANCH=main + +# Set Global ENV +ENV MAMBA_ROOT_PREFIX=/opt/conda +ENV PATH=$MAMBA_ROOT_PREFIX/bin:$PATH +ENV MAMBA_DOCKERFILE_ACTIVATE=1 + +# Switch to root to install dependencies USER root -# Create a login user named rmguser -# Create a login user named rmguser -ARG NEW_MAMBA_USER=rmguser -ARG NEW_MAMBA_USER_ID=1000 -ARG NEW_MAMBA_USER_GID=1000 -RUN usermod "--login=${NEW_MAMBA_USER}" "--home=/home/${NEW_MAMBA_USER}" \ - --move-home "-u ${NEW_MAMBA_USER_ID}" "${MAMBA_USER}" && \ - groupmod "--new-name=${NEW_MAMBA_USER}" \ - "-g ${NEW_MAMBA_USER_GID}" "${MAMBA_USER}" && \ - echo "${NEW_MAMBA_USER}" > "/etc/arg_mamba_user" && \ - : - -# Set the environment variables -ARG MAMBA_ROOT_PREFIX=/opt/conda -ENV MAMBA_USER=$NEW_MAMBA_USER -ENV BASE=$MAMBA_ROOT_PREFIX - -# Install system dependencies -# -# List of deps and why they are needed: -# - make, gcc, g++ for building RMG -# - git for downloading RMG respoitories -# - wget for downloading conda install script -# - libxrender1 required by RDKit -# Clean up the apt cache to reduce the size of the image RUN apt-get update && apt-get install -y \ - git \ - gcc \ - g++ \ - make \ - libgomp1\ - libxrender1 \ - sudo \ - nano \ - && apt-get clean \ - && apt-get autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo "${NEW_MAMBA_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers -# Change user to the non-root user -USER $MAMBA_USER - -# Make directory for RMG-Py and RMG-database -RUN mkdir -p /home/rmguser/Code - -# Change working directory to Code -WORKDIR /home/rmguser/Code - -# ------------------------------------------------------------------ clone & checkout -WORKDIR /home/rmguser/Code -RUN git clone --filter=blob:none --no-checkout https://github.com/ReactionMechanismGenerator/RMG-Py.git RMG-Py \ - && git -C RMG-Py checkout --detach 55464c54d1fa61b531e865682df598d33718597d \ - && git clone --filter=blob:none --depth 1 https://github.com/ReactionMechanismGenerator/RMG-database.git RMG-database - - - - -ENV PATH=/opt/conda/envs/rmg_env/bin:/home/rmguser/Code/RMG-Py:$PATH \ - PYTHONPATH=/home/rmguser/Code/RMG-Py - - -ENV JULIA_DEPOT_PATH="/home/rmguser/julia-ver/packages" -ENV JULIA_HISTORY=/home/rmguser/repl_history.jl -# Since 1.9.0 Julia, the CPU target is set to "native" by default. This is not ideal for a Docker image, so we set it to a list of common CPU targets -# This avoids the need to compile the Julia packages for the specific CPU architecture of the host machine -ENV JULIA_CPU_TARGET="x86-64,haswell,skylake,broadwell,znver1,znver2,znver3,cascadelake,icelake-client,cooperlake,generic,native" -# Install RMS -# The extra arguments are required to install PyCall and RMS in this Dockerfile. Will not work without them. -# Final command is to compile the RMS during Docker build - This will reduce the time it takes to run RMS for the first time -# Julia + PyCall + RMS in rmg_env -ENV CONDA_JL_CONDA_EXE=/opt/conda/envs/rmg_env/bin/conda -# ------------------------------------------------------------------ env create -WORKDIR /home/rmguser/Code/RMG-Py -RUN micromamba create -y -n rmg_env -f environment.yml \ - && touch /opt/conda/envs/rmg_env/condarc-julia.yml \ - && micromamba install -y -n rmg_env -c conda-forge conda \ - && micromamba clean -a -y \ - && echo "export PYTHONPATH=/home/rmguser/Code/RMG-Py" >> ~/.bashrc \ - && echo "export PATH=/home/rmguser/Code/RMG-Py:$PATH" >> ~/.bashrc \ - && micromamba run -n rmg_env make -j"$(nproc)" \ - && micromamba run -n rmg_env bash -lc "\ - julia -e 'ENV[\"CONDA_JL_CONDA_EXE\"]=\"${CONDA_JL_CONDA_EXE}\"; using Pkg; \ - Pkg.add(PackageSpec(name=\"PyCall\", rev=\"master\")); Pkg.build(\"PyCall\"); \ - Pkg.add(PackageSpec(name=\"ReactionMechanismSimulator\", url=\"https://github.com/ReactionMechanismGenerator/ReactionMechanismSimulator.jl\", rev=\"8dbb07eedebaacf9b9f77081623c572d054b9a6f\")); \ - using ReactionMechanismSimulator'; \ - python -c 'import julia, diffeqpy; julia.install(); diffeqpy.install()'; \ - python-jl -c 'from pyrms import rms' \ - " - -RUN micromamba run -n rmg_env python-jl /home/rmguser/Code/RMG-Py/rmg.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py \ -# delete the results, preserve input.py -&& mv /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py /home/rmguser/Code/RMG-Py/examples/input.py \ -&& rm -rf /home/rmguser/Code/RMG-Py/examples/rmg/minimal/* \ -&& mv /home/rmguser/Code/RMG-Py/examples/input.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py - -# Installing ARC -# Change directory to Code -WORKDIR /home/rmguser/Code - -# Clone main branch ARC repository from GitHub and set as working directory -RUN git clone -b main https://github.com/ReactionMechanismGenerator/ARC.git -WORKDIR /home/rmguser/Code/ARC - -# Set environment variables for the Docker run and container -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/ARC" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/AutoTST" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/TS-GCN" -ENV PATH=/home/rmguser/Code/ARC:$PATH - -# Install ARC Environment -COPY --chown=rmguser:rmguser ./environment.yml /home/rmguser/Code/ARC/environment.yml + git gcc g++ make wget libxrender1 ca-certificates sudo nano make && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + mkdir -p /home/mambauser/Code && \ + chown -R mambauser:mambauser /home/mambauser && \ + chown -R mambauser:mambauser /opt/conda && \ + chown -R mambauser:mambauser /home/mambauser + +# Change to unprivileged user +USER mambauser +ENV MAMBA_USER=mambauser + +# Set JuliaUp PATH and install Julia 1.10 as req. by RMG +ENV PATH="/home/mambauser/.juliaup/bin:$PATH" +RUN wget -qO- https://install.julialang.org | sh -s -- --yes --default-channel 1.10 + +# Switch directory to Code and RMG clone +WORKDIR /home/mambauser/Code +RUN git clone --branch ${RMG_PY_BRANCH} https://github.com/ReactionMechanismGenerator/RMG-Py.git && \ + git clone --branch ${RMG_DATABASE_BRANCH} https://github.com/ReactionMechanismGenerator/RMG-database.git && \ + git clone --branch ${ARC_BRANCH} https://github.com/ReactionMechanismGenerator/ARC.git + +# Create RMG-Py environment +RUN micromamba create -y -n rmg_env -f /home/mambauser/Code/RMG-Py/environment.yml && \ + micromamba run -n rmg_env micromamba install -y -c conda-forge pyjuliacall conda && \ + micromamba clean --all --yes && \ + micromamba run -n rmg_env make -C /home/mambauser/Code/RMG-Py -j"$(nproc)" && \ + micromamba run -n rmg_env bash -c "\ + cd /home/mambauser/Code/RMG-Py && \ + source install_rms.sh \ + " + +WORKDIR /home/mambauser/Code/ARC RUN micromamba create -y -n arc_env -f environment.yml --channel-priority flexible && \ + micromamba install -y -n arc_env -c conda-forge pytest && \ micromamba clean --all -f -y && \ -bash -euxo pipefail <<'EOF' -PYDIR=$(echo /opt/conda/envs/arc_env/lib/python*/site-packages) - -# cache -rm -rf /home/rmguser/.cache/pip /home/rmguser/.cache/yarn - -# strip compiled cruft -find "$PYDIR" -type f \( -name "*.a" -o -name "*.py[co]" \) -delete -find "$PYDIR" -type d -name "__pycache__" -prune -exec rm -rf {} + + micromamba run -n arc_env bash -euxo pipefail -c \ + "make compile && bash ./devtools/install_pyrdl.sh" && \ + micromamba clean --all --yes + +# Stage 2: Final image +# The final image is based on the same micromamba image, but we copy over the installed RMG and ARC from the builder stage. +# This keeps the final image size smaller and avoids unnecessary layers. +FROM --platform=linux/amd64 mambaorg/micromamba:2.2-ubuntu24.04 + +ENV MAMBA_ROOT_PREFIX=/opt/conda +ENV PATH=$MAMBA_ROOT_PREFIX/bin:/home/mambauser/.juliaup/bin:/home/mambauser/Code/RMG-Py:/home/mambauser/Code/ARC:$PATH +ENV PYTHONPATH="/home/mambauser/Code/RMG-Py:/home/mambauser/Code/ARC" +ENV RMG_PY_DIR="/home/mambauser/Code/RMG-Py" +ENV ARC_DIR="/home/mambauser/Code/ARC" +ENV MAMBA_DOCKERFILE_ACTIVATE=1 -# drop test directories in one traversal -find "$PYDIR" -type d \( -path "*/scipy/tests" -o -path "*/numpy/tests" -o -path "*/pandas/tests" \) \ - -prune -exec rm -rf {} + - -# remove cython sources -find "$PYDIR" -name "*.pyx" -delete -rm -f "$PYDIR/uvloop/loop.c" -EOF - -FROM --platform=linux/amd64 mambaorg/micromamba@sha256:20fb02f2d1160265f7fabaf1601707a902ae65c6dc9e053d305441182450c368 AS arc-stage USER root -ARG NEW_MAMBA_USER=rmguser -ARG NEW_MAMBA_USER_ID=1000 -ARG NEW_MAMBA_USER_GID=1000 -RUN usermod "--login=${NEW_MAMBA_USER}" "--home=/home/${NEW_MAMBA_USER}" \ - --move-home "-u ${NEW_MAMBA_USER_ID}" "${MAMBA_USER}" && \ - groupmod "--new-name=${NEW_MAMBA_USER}" \ - "-g ${NEW_MAMBA_USER_GID}" "${MAMBA_USER}" && \ - echo "${NEW_MAMBA_USER}" > "/etc/arg_mamba_user" && \ - : - -# Set the environment variables -ARG MAMBA_ROOT_PREFIX=/opt/conda -ENV MAMBA_USER=$NEW_MAMBA_USER -ENV BASE=$MAMBA_ROOT_PREFIX - -# Install system dependencies -# -# List of deps and why they are needed: -# - make, gcc, g++ for building RMG -# - git for downloading RMG respoitories -# - wget for downloading conda install script -# - libxrender1 required by RDKit -# Clean up the apt cache to reduce the size of the image -RUN apt-get update && apt-get install -y \ - git \ - gcc \ - g++ \ - make \ - libgomp1\ - libxrender1 \ - sudo \ - nano \ - && apt-get clean \ - && apt-get autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo "${NEW_MAMBA_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ - && printf '\n# print cheat-sheet once per interactive Bash\nif [[ $- == *i* && $SHLVL -eq 1 ]]; then\n aliases\nfi\n' \ - >> /etc/bash.bashrc -# Change user to the non-root user -USER $MAMBA_USER - -# Make directory for RMG-Py and RMG-database -RUN mkdir -p /home/rmguser/Code - -COPY --from=rmg-stage --chown=rmguser:rmguser /opt/conda /opt/conda -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/RMG-Py /home/rmguser/Code/RMG-Py -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/RMG-database /home/rmguser/Code/RMG-database -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/ARC /home/rmguser/Code/ARC - - -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/ARC" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/AutoTST" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/TS-GCN" -ENV PATH=/home/rmguser/Code/ARC:/home/rmguser/Code/RMG-Py:/home/rmguser/Code/RMG-database:$PATH -ENV PYTHONPATH=/home/rmguser/Code/ARC:/home/rmguser/Code/RMG-Py:/home/rmguser/Code/RMG-database:$PYTHONPATH - -ENV JULIA_DEPOT_PATH="/home/rmguser/julia-ver/packages" -ENV JULIA_HISTORY=/home/rmguser/repl_history.jl -# Since 1.9.0 Julia, the CPU target is set to "native" by default. This is not ideal for a Docker image, so we set it to a list of common CPU targets -# This avoids the need to compile the Julia packages for the specific CPU architecture of the host machine -ENV JULIA_CPU_TARGET="x86-64,haswell,skylake,broadwell,znver1,znver2,znver3,cascadelake,icelake-client,cooperlake,generic" -# Install RMS -# The extra arguments are required to install PyCall and RMS in this Dockerfile. Will not work without them. -# Final command is to compile the RMS during Docker build - This will reduce the time it takes to run RMS for the first time -# Julia + PyCall + RMS in rmg_env -ENV CONDA_JL_CONDA_EXE=/opt/conda/envs/rmg_env/bin/conda - -WORKDIR /home/rmguser/ - -RUN mkdir -p /home/rmguser/.arc && \ - cp /home/rmguser/Code/ARC/arc/settings/settings.py /home/rmguser/.arc/settings.py && \ - cp /home/rmguser/Code/ARC/arc/settings/submit.py /home/rmguser/.arc/submit.py - -# Copy alias_print.sh and entrywrapper.sh to the container -# Copy your login‐wide aliases into /etc/profile.d -COPY --chown=rmguser:rmguser dockerfiles/aliases.sh /etc/profile.d/99-rmg-aliases.sh - -# Copy the cheat‐sheet and entrypoint -COPY --chown=rmguser:rmguser dockerfiles/aliases_print.sh /usr/local/bin/aliases -COPY --chown=rmguser:rmguser dockerfiles/entrywrapper.sh /home/rmguser/entrywrapper.sh - -# Fix permissions & make the scripts executable & ensure rms is run once -RUN chmod 644 /etc/profile.d/99-rmg-aliases.sh \ - && chmod +x /home/rmguser/entrywrapper.sh \ - && chmod +x /usr/local/bin/aliases \ - && micromamba run -n rmg_env python-jl /home/rmguser/Code/RMG-Py/rmg.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py \ - # delete the results, preserve input.py - && mv /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py /home/rmguser/Code/RMG-Py/examples/input.py \ - && rm -rf /home/rmguser/Code/RMG-Py/examples/rmg/minimal/* \ - && mv /home/rmguser/Code/RMG-Py/examples/input.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py - -# Use a login shell so /etc/profile and /etc/profile.d/* are sourced automatically -ENTRYPOINT ["bash","-l","/home/rmguser/entrywrapper.sh"] - -# Activate the ARC environment -ARG MAMBA_DOCKERFILE_ACTIVATE=1 -ENV ENV_NAME=arc_env +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git \ + libxrender1 \ + ca-certificates \ + nano \ + make \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +USER mambauser + +COPY --from=builder --chown=mambauser:mambauser /opt/conda /opt/conda +COPY --from=builder --chown=mambauser:mambauser /home/mambauser/.juliaup /home/mambauser/.juliaup +COPY --from=builder --chown=mambauser:mambauser /home/mambauser/Code /home/mambauser/Code + +# Need to copy the tests separately as they are not in the ARC git by default +COPY --chown=mambauser:mambauser dockerfiles/docker_tests /home/mambauser/Code/ARC/dockerfiles/docker_tests + +# --- Entry wrapper ---------------------------------------------------------- +COPY --chmod=755 dockerfiles/entrywrapper.sh /usr/local/bin/entrywrapper.sh +COPY --chmod=644 dockerfiles/aliases.sh /etc/profile.d/aliases.sh +COPY --chmod=755 dockerfiles/aliases_print.sh /usr/local/bin/aliases + +ENTRYPOINT ["/usr/local/bin/entrywrapper.sh"] From 5561c64316614448bd4f907ac9b77e0395461f4c Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sun, 28 Sep 2025 20:30:50 +0300 Subject: [PATCH 2/4] Enables user execution of ARC/RMG commands Introduces an entrypoint script to allow users to run ARC and RMG commands directly without needing to manually activate environments or specify the full path to the executables. This change also corrects the user home directory to `/home/mambauser` and simplifies the `rmg` alias by using `python` instead of `python-jl`. --- dockerfiles/aliases.sh | 6 +- dockerfiles/aliases_print.sh | 2 +- dockerfiles/entrywrapper.sh | 104 ++++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 25 deletions(-) diff --git a/dockerfiles/aliases.sh b/dockerfiles/aliases.sh index 7e56be1fb7..072762c53c 100644 --- a/dockerfiles/aliases.sh +++ b/dockerfiles/aliases.sh @@ -13,9 +13,9 @@ alias rmge='micromamba activate rmg_env' alias arce='micromamba activate arc_env' # code roots (set once at image build) -export rmgpy_path=/home/rmguser/Code/RMG-Py -export rmgdb_path=/home/rmguser/Code/RMG-database -export arc_path=/home/rmguser/Code/ARC +export rmgpy_path=/home/mambauser/Code/RMG-Py +export rmgdb_path=/home/mambauser/Code/RMG-database +export arc_path=/home/mambauser/Code/ARC alias rmgcode='cd "$rmgpy_path"' alias dbcode='cd "$rmgdb_path"' diff --git a/dockerfiles/aliases_print.sh b/dockerfiles/aliases_print.sh index 44ed7eb6aa..d3532506e9 100644 --- a/dockerfiles/aliases_print.sh +++ b/dockerfiles/aliases_print.sh @@ -22,7 +22,7 @@ cat <<'EOF' arcode → cd $arc_path ‣ One-liner runners - rmg → python-jl $rmgpy_path/rmg.py input.py + rmg → python $rmgpy_path/rmg.py input.py arkane → python $rmgpy_path/Arkane.py input.py arc → python $arc_path/ARC.py input.yml arcrestart → python $arc_path/ARC.py restart.yml diff --git a/dockerfiles/entrywrapper.sh b/dockerfiles/entrywrapper.sh index 388315afc2..516783ba15 100644 --- a/dockerfiles/entrywrapper.sh +++ b/dockerfiles/entrywrapper.sh @@ -1,34 +1,96 @@ #!/usr/bin/env bash set -euo pipefail -# 1) Micromamba setup (for both interactive & non-interactive) -eval "$(micromamba shell hook --shell bash)" +# 0) Only try chown if we're root (bind-mounts may be root-owned) +if [[ "$(id -u)" -eq 0 ]] && [[ ! -O /home/mambauser/Code ]]; then + chown -R mambauser:mambauser /home/mambauser/Code || true +fi -# 1.1) Ensure the Code directory is owned by rmguser -if [ ! -O /home/rmguser/Code ]; then - chown -R rmguser:rmguser /home/rmguser/Code +# If running non-interactively at container root and /work exists; it will go there +# This helps when users forget `-w /work` +if [[ -d /work && "${PWD:-/}" == "/" && ! -t 0 ]]; then + cd /work || true fi -# 2) Interactive mode: login shells or docker exec … bash -if [ -t 0 ] && [ -t 1 ]; then -# # Show the aliases cheat-sheet -# /home/rmguser/.aliases_print.sh - # Drop into an interactive Bash +# 1) If no args → interactive shell (when run with -it) +if [[ $# -eq 0 ]]; then exec /bin/bash -l fi -# 3) Non-interactive command mode: rmg or arc -case "${1:-}" in - rmg) - micromamba activate rmg_env - exec python-jl /home/rmguser/Code/RMG-Py/rmg.py "${2:-input.py}" - ;; - arc) - micromamba activate arc_env - exec python /home/rmguser/Code/ARC/ARC.py "${2:-input.yml}" +usage() { + cat >&2 <<'USAGE' +Usage: + arc [flags] + rmg [flags] + +Run with a bind mount so the container can read your input file, e.g. + + # ARC + docker run --rm -v "$PWD:/work" -w /work IMAGE arc my_case/input.yml + + # RMG + docker run --rm -v "$PWD:/work" -w /work IMAGE rmg my_case/input.py + +Notes: +- / must be a non-flag argument +- if you pass flags (e.g. -n 8), put them before the file: rmg -n 8 input.py +USAGE +} + + +# Return the first non-flag arg +first_nonflag() { + local after_ddash="no" + for a in "$@"; do + if [[ "$after_ddash" == "no" && "$a" == "--" ]]; then + after_ddash="yes" + continue + fi + if [[ "$after_ddash" == "yes" || "$a" != -* ]]; then + echo "$a" + return 0 + fi + done + return 1 +} + +# Show usage on -h/--help for arc/rmg if no file was given +wants_help_no_file() { + for a in "$@"; do + [[ "$a" == "-h" || "$a" == "--help" ]] && return 0 + done + return 1 +} + +# 2) Subcommands: rmg / arc +cmd="$1"; shift || true +case "$cmd" in + arc|rmg) + if wants_help_no_file "$@" && ! first_nonflag "$@" >/dev/null; then + usage + exit 0 + fi + if ! file_arg="$(first_nonflag "$@")"; then + usage + exit 64 # EX_USAGE + fi + + if [[ ! -f "$file_arg" ]]; then + echo "Error: input file not found inside container: $file_arg" >&2 + echo "Tip: mount and set workdir, e.g.: docker run -v \"\$PWD:/work\" -w /work IMAGE $cmd $file_arg" >&2 + exit 66 # EX_NOINPUT + fi + + # no defaults: user must provide their file path + if [[ "$cmd" == "arc" ]]; then + exec micromamba run -n arc_env python /home/mambauser/Code/ARC/ARC.py "$@" + else + exec micromamba run -n rmg_env python /home/mambauser/Code/RMG-Py/rmg.py "$@" + fi ;; + *) - echo "Usage: [input-file]" >&2 - exit 1 + # Pass-through for CI or ad-hoc shell commands + exec "$cmd" "$@" ;; esac From e5b90e136d7a8aaeb19d9df97b6f6914b93b601a Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sun, 28 Sep 2025 20:31:06 +0300 Subject: [PATCH 3/4] Adds smoke tests for docker images Adds smoke tests to verify basic functionality of ARC, Arkane, and RMG-Py within the Docker images. These tests ensure that the core components can be imported and that the command-line interfaces are operational. It also checks basic execution capabilities of ARC/Arkane. This provides a quick way to check the health of the docker images after building. --- dockerfiles/docker_tests/test_docker_smoke.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 dockerfiles/docker_tests/test_docker_smoke.py diff --git a/dockerfiles/docker_tests/test_docker_smoke.py b/dockerfiles/docker_tests/test_docker_smoke.py new file mode 100644 index 0000000000..fddb032879 --- /dev/null +++ b/dockerfiles/docker_tests/test_docker_smoke.py @@ -0,0 +1,75 @@ +import pytest +import subprocess +import tempfile +import pathlib +import textwrap + + +@pytest.mark.smoke +def test_import_arc(): + """Test that ARC can be imported in the docker image.""" + try: + import arc + assert hasattr(arc, '__file__') + except ImportError as e: + pytest.fail(f"ImportError: {e}") + + +@pytest.mark.smoke +def test_arc_cli_help_runs(): + """Test that ARC CLI help runs in the docker image.""" + cmd = ["bash", "-lc", "micromamba run -n arc_env python -m ARC --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "help" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_arkane_cli_help_runs(): + """Test that Arkane CLI help runs in the docker image.""" + cmd = ["bash", "-lc","micromamba run -n rmg_env python -m arkane --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "arkane" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_arc_can_execute_arkane_minimal(): + """Test that ARC can execute Arkane with a minimal input in the docker image.""" + arkane_input = textwrap.dedent("""\ + #!/usr/bin/env python + modelChemistry = 'wb97m-v/def2-tzvpd' # irrelevant here, just parseable + useHinderedRotors = False + thermo('H2', 'H298') + """) + with tempfile.TemporaryDirectory() as td: + inp = pathlib.Path(td, "input.py") + inp.write_text(arkane_input) + # Call arkane via rmg_env the same way ARC would (subprocess) + cmd = ["bash", "-lc", f"micromamba run -n rmg_env python -m arkane {inp} || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # We only assert it runs and produces any Arkane header/output (no heavy calc) + assert "arkane" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_rmgpy_imports(): + """Test that RMG-Py can be imported in the docker image.""" + code = r""" +import importlib, sys +m = importlib.import_module('rmgpy') +print('rmgpy OK', getattr(m, '__version__', 'unknown')) +""" + cmd = ["bash", "-lc", f"micromamba run -n rmg_env python - <<'PY'\n{code}\nPY"] + p = subprocess.run(cmd, capture_output=True, text=True) + assert p.returncode == 0, p.stderr + assert "rmgpy OK" in p.stdout + + +@pytest.mark.smoke +def test_rmg_cli_help_runs(): + """Test that RMG CLI help runs in the docker image.""" + cmd = ["bash", "-lc", "micromamba run -n rmg_env rmg --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "rmg" in (p.stdout + p.stderr).lower() From 3f5164a8902d6a98dc8d00ce2d74e8f47abcef57 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sun, 28 Sep 2025 20:31:16 +0300 Subject: [PATCH 4/4] Updates Docker build workflow for ARC Refactors the Docker build workflow to improve build process and smoke test execution. The changes include: - Updates checkout and build-push-action versions. - Introduces named test stage for the main branch. - Adjusts smoke test execution for main and non-main branches using the test stage image. - Streamlines Docker Hub login and push steps for main branch builds. --- .github/workflows/docker_build.yml | 55 ++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 55b2adf8d4..ae66a06ed5 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -32,33 +32,68 @@ jobs: large-packages: true docker-images: false swap-storage: true + - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5.0.0 + - name: Set up Buildx + uses: docker/setup-buildx-action@v3.11.1 + + # ----- PR and non-main branch steps ----- + # For PRs: Build image but do not push and run smoke tests - name: Build Docker Image (No Push) if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref != 'refs/heads/main') - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: ./Dockerfile push: false + load: true + tags: arc:test + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Smoke Tests + if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref != 'refs/heads/main') + run: | + docker run --rm arc:test bash -lc \ + "cd /home/mambauser/Code/ARC && micromamba run -n arc_env pytest dockerfiles/docker_tests/test_docker_smoke.py -m smoke -q" + + # ----- Main branch only steps ----- + # For pushes to main: Build, run smoke tests, and push to Docker Hub + - name: Build test stage (main) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/build-push-action@v6.18.0 + with: + context: . + file: ./Dockerfile + load: true + tags: arc:final-${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run smoke (main, test stage) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: | + docker run --rm arc:final-${{ github.sha }} bash -lc \ + "cd /home/mambauser/Code/ARC && micromamba run -n arc_env pytest dockerfiles/docker_tests/test_docker_smoke.py -m smoke -q" - name: Login to Docker Hub if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.5.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Docker Buildx - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/setup-buildx-action@v3.0.0 - - - name: Build and Push Docker Image + - name: Build final and push (main) if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: ./Dockerfile push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/arc:latest + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/arc:latest + ${{ secrets.DOCKERHUB_USERNAME }}/arc:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max