From be6d21cb27aa830c3261d2a9c2c604bcfae03f7b Mon Sep 17 00:00:00 2001 From: Jas Kalayan Date: Wed, 17 Jun 2026 10:41:15 +0100 Subject: [PATCH 1/3] update dockerfile and handle env variables in app --- biosimdb_interface/__init__.py | 12 ++-- biosimdb_interface/schema/webform.py | 10 ++- docker/Dockerfile | 99 +++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/biosimdb_interface/__init__.py b/biosimdb_interface/__init__.py index 1ecbea1..d8beb6b 100644 --- a/biosimdb_interface/__init__.py +++ b/biosimdb_interface/__init__.py @@ -44,11 +44,11 @@ def create_app(test_config=None): UPLOAD_FOLDER=os.getenv("UPLOAD_FOLDER", "/tmp"), # App specific CLIENT_ID=os.getenv("CLIENT_ID", ""), CLIENT_SECRET=os.getenv("CLIENT_SECRET", ""), - AUTH_URL=os.getenv("AUTH_URL"), - TOKEN_URL=os.getenv("TOKEN_URL"), - BASE_URL=os.getenv("BASE_URL"), - API_BASE=os.getenv("API_BASE"), - REDIRECT_URI=os.getenv("REDIRECT_URI"), + AUTH_URL=os.getenv("AUTH_URL", ""), + TOKEN_URL=os.getenv("TOKEN_URL", ""), + BASE_URL=os.getenv("BASE_URL", ""), + API_BASE=os.getenv("API_BASE", ""), + REDIRECT_URI=os.getenv("REDIRECT_URI", ""), SCOPES=os.getenv("SCOPES", "").strip(), ) # invenio app configs @@ -61,7 +61,7 @@ def create_app(test_config=None): @app.context_processor def inject_base_url(): - return {"BASE_URL": app.config.get("BASE_URL", "")} + return {"BASE_URL": app.config.get("BASE_URL") or ""} # ensure the instance folder exists try: diff --git a/biosimdb_interface/schema/webform.py b/biosimdb_interface/schema/webform.py index b5998c0..cde6ef0 100644 --- a/biosimdb_interface/schema/webform.py +++ b/biosimdb_interface/schema/webform.py @@ -32,12 +32,16 @@ def get_simulation_metadata(): """ path = os.getenv("WEBFORM_SCHEMA_PATH") if not path: - # Return an empty schema or raise a more descriptive error if preferred - return {} + raise RuntimeError("WEBFORM_SCHEMA_PATH environment variable is not set.") + if not os.path.exists(path): + raise FileNotFoundError(f"Schema file not found at: {path}") mtime = os.path.getmtime(path) if _cache["mtime"] != mtime: - _cache["schema"] = SchemaPopulator(schema_path=path).load_schema() + schema = SchemaPopulator(schema_path=path).load_schema() + if not schema: + raise ValueError(f"Schema file at {path} is empty or invalid.") + _cache["schema"] = schema _cache["mtime"] = mtime return _cache["schema"] diff --git a/docker/Dockerfile b/docker/Dockerfile index 2cad6e9..7ad3b0c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,25 +1,94 @@ -FROM python:3.12-slim +# Stage 1: Fetch Schema Artifacts +FROM ubuntu:25.10 AS schema-fetcher -WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends curl jq tar ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +WORKDIR /tmp + +RUN TAG=$(curl -fsSL https://api.github.com/repos/CCPBioSim/biosim-schema/releases/latest | jq -r .tag_name) && \ + curl -fsSL -o artifacts.tar.gz \ + "https://github.com/CCPBioSim/biosim-schema/releases/download/${TAG}/biosim-schema-artifacts.tar.gz" && \ + mkdir -p /opt/biosim-schema && \ + tar -xzf artifacts.tar.gz -C /opt/biosim-schema + + +# Stage 2: Build Python dependencies +FROM ubuntu:25.10 AS builder + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 -# Install build dependencies for MDAnalysis RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - g++ \ + python3 \ + python3-pip \ + python3-venv \ + python3-dev \ + build-essential \ + libgomp1 \ && rm -rf /var/lib/apt/lists/* -# Install the package -COPY pyproject.toml . -COPY README.md . -COPY LICENSE . +# Create virtual environment +RUN python3 -m venv /venv + +# Activate venv and install dependencies +ENV PATH="/venv/bin:$PATH" + +WORKDIR /app + +COPY pyproject.toml README.md LICENSE ./ COPY biosimdb_interface/ biosimdb_interface/ -RUN pip install --no-cache-dir . -# Copy schema files at a known path (mounted at runtime for dev — see below) -# COPY ../biosim-schema /biosim-schema +# Install application and its dependencies into the venv +RUN pip install --upgrade pip setuptools && \ + pip install --no-cache-dir gunicorn . + + +# Stage 3: Runtime +FROM ubuntu:25.10 + +ARG PORT=5002 + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3-minimal \ + libgomp1 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN groupadd -g 10001 biosim && \ + useradd \ + --uid 10001 \ + --gid biosim \ + --create-home \ + --shell /usr/sbin/nologin \ + biosim + +WORKDIR /app + +# Copy the pre-built virtual environment from the builder stage +COPY --from=builder --chown=biosim:biosim /venv /venv + +# Copy schema artifacts from the fetcher stage +COPY --from=schema-fetcher --chown=biosim:biosim /opt/biosim-schema /opt/biosim-schema + +# Set PATH to include the virtual environment's bin directory +ENV PATH="/venv/bin:$PATH" + +USER biosim -EXPOSE 5002 +ENV PORT=${PORT} \ + FLASK_APP=biosimdb_interface \ + PYSTOW_HOME=/app/.pystow \ + WEBFORM_SCHEMA_PATH=/opt/biosim-schema/project/schema_webformfields.json \ + ENGINE_MAPPING_SCHEMA_PATH=/opt/biosim-schema/project/schema_enginemappings.json \ + BIOSIM_SCHEMA_PATH=/opt/biosim-schema/biosim_schema/schema/biosim_schema.yaml -ENV FLASK_APP=biosimdb_interface +EXPOSE ${PORT} -CMD ["gunicorn", "--bind", "0.0.0.0:5002", "biosimdb_interface:create_app()"] +CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:$PORT 'biosimdb_interface:create_app()'"] From d847a03026b3efc037f319e02ee39d700bae2479 Mon Sep 17 00:00:00 2001 From: Jas Kalayan Date: Wed, 17 Jun 2026 10:47:15 +0100 Subject: [PATCH 2/3] return empty schema, ensures tests work --- biosimdb_interface/schema/webform.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/biosimdb_interface/schema/webform.py b/biosimdb_interface/schema/webform.py index cde6ef0..3f6c9bc 100644 --- a/biosimdb_interface/schema/webform.py +++ b/biosimdb_interface/schema/webform.py @@ -31,17 +31,13 @@ def get_simulation_metadata(): dict: Parsed simulation metadata schema. """ path = os.getenv("WEBFORM_SCHEMA_PATH") - if not path: - raise RuntimeError("WEBFORM_SCHEMA_PATH environment variable is not set.") - if not os.path.exists(path): - raise FileNotFoundError(f"Schema file not found at: {path}") + if not path or not os.path.exists(path): + # Return an empty schema + return {} mtime = os.path.getmtime(path) if _cache["mtime"] != mtime: - schema = SchemaPopulator(schema_path=path).load_schema() - if not schema: - raise ValueError(f"Schema file at {path} is empty or invalid.") - _cache["schema"] = schema + _cache["schema"] = SchemaPopulator(schema_path=path).load_schema() _cache["mtime"] = mtime return _cache["schema"] From a985f8a211d858746da27ce62c610408efc429ff Mon Sep 17 00:00:00 2001 From: Jas Kalayan Date: Wed, 17 Jun 2026 12:08:13 +0100 Subject: [PATCH 3/3] add docker publish workflow --- .github/workflows/docker-publish.yaml | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/docker-publish.yaml diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml new file mode 100644 index 0000000..e01b804 --- /dev/null +++ b/.github/workflows/docker-publish.yaml @@ -0,0 +1,62 @@ +name: Publish Docker image to GHCR + +on: + repository_dispatch: + types: [build] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-docker-image: + runs-on: ubuntu-25.10 + permissions: + contents: read + packages: write # Required to push images to GHCR + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Fetch external versions and build date + id: vars + shell: bash + run: | + echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT + echo "schema=$(gh release view --repo CCPBioSim/biosim-schema --json tagName --template '{{.tagName}}')" >> $GITHUB_OUTPUT + echo "extractor=$(gh release view --repo CCPBioSim/biosim-extractor --json tagName --template '{{.tagName}}')" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.BUILD_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha + labels: | + org.opencontainers.image.created=${{ steps.vars.outputs.date }} + biosim.schema.version=${{ steps.vars.outputs.schema }} + biosim.extractor.version=${{ steps.vars.outputs.extractor }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max