Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
"helpers:pinGitHubActionDigestsToSemver"
],
"timezone": "Europe/London",
"schedule": ["after 5am and before 5pm every weekday"],
"dependencyDashboard": true,
"assignees": ["jimboid"],
"minimumReleaseAge": "3 days",
"packageRules": [
{
"matchManagers": ["github-actions"],
"groupName": "GitHub Actions",
"addLabels": ["ci", "dependencies"]
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/(^|/)Dockerfile[^/]*$/"
],
"matchStringsStrategy": "recursive",
"matchStrings": [
"pip3? install[\\s\\S]*?(?<packages>(?:[\\w.-]+==[\\w.-]+[\\s\\\\]*)+)",
"(?<depName>[\\w.-]+)==(?<currentValue>[\\w.-]+)"
],
"datasourceTemplate": "pypi",
"versioningTemplate": "pep440"
},
{
"customType": "regex",
"managerFilePatterns": [
"/(^|/)Dockerfile[^/]*$/"
],
"matchStringsStrategy": "recursive",
"matchStrings": [
"(?:mamba|conda) install[\\s\\S]*?(?<packages>(?:(?:[\\w-]+::)?[\\w.-]+=[\\w.-]+[\\s\\\\]*)+)",
"(?:(?<channel>[\\w-]+)::)?(?<depName>[\\w.-]+)=(?<currentValue>[\\w.-]+)"
],
"datasourceTemplate": "conda",
"registryUrlTemplate": "https://api.anaconda.org/package/{{#if channel}}{{channel}}{{else}}conda-forge{{/if}}/",
"versioningTemplate": "pep440"
}
]
}
179 changes: 179 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: ci/cd
on:
pull_request:
repository_dispatch:
types: [build]
workflow_dispatch:

jobs:
build:
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
name: build ${{ matrix.platform }}
outputs:
tag: ${{ steps.envvars.outputs.tag }}
steps:
- name: "node-cleanup"
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
sudo docker builder prune -a

- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Prepare env
id: envvars
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
if [ ${{ github.event.client_payload.tag }} != 'null' ]; then
echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT
else
echo "tag=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
fi

- name: Metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0

- name: Build and push by digest
if: github.event_name == 'pull_request'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
file: ./docker/Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}
outputs: type=docker,push-by-digest=true,name-canonical=true,push=true

# - name: Run tests
# if: github.event_name == 'pull_request'
# shell: bash
# run: |
# docker run -t ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:latest bash -c " \
# pip install pytest nbmake; \
# find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600;"

- name: Authenticate with GHCR
if: github.event_name != 'pull_request'
id: auth
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.BUILD_TOKEN }}

- name: Build and push by digest
if: github.event_name != 'pull_request'
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
file: ./docker/Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}
outputs: type=image,push-by-digest=true,name-canonical=true,push=true

# - name: Run tests
# if: github.event_name != 'pull_request'
# shell: bash
# run: |
# docker run -t ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@${{ steps.build.outputs.digest }} bash -c " \
# pip install pytest nbmake; \
# find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600;"

- name: Export digest
if: github.event_name != 'pull_request'
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"

- name: Upload digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

tags:
if: github.event_name != 'pull_request'
runs-on: ubuntu-24.04
name: merge and tag
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true

- name: Authenticate with GHCR
id: auth
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.BUILD_TOKEN }}

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0

- name: Metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}
tags: |
latest
${{ needs.build.outputs.tag }}

- name: Create manifest list and push
id: annotate
continue-on-error: true
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
--annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
--annotation='index:org.opencontainers.image.licenses=MIT' \
--annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
--annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
--annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
$(printf 'ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@sha256:%s ' *)

- name: Create manifest list and push without annotations
if: steps.annotate.outcome == 'failure'
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@sha256:%s ' *)

- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:latest

# - name: Post version update to dash
# uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
# with:
# token: ${{ secrets.BUILD_TOKEN }}
# repository: ccpbiosim/ccpbiosim.github.io
# event-type: build
# client-payload: '{"repo": "${{ github.event.repository.name }}", "tag": "${{ needs.build.outputs.tag }}"}'
28 changes: 28 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Start with BioSim base image.
ARG BASE_IMAGE=latest@sha256:5eb67b09e3fdf64b3128bd9501e08a0cbb007804b678f2096728d8c1260e6217
FROM ghcr.io/ccpbiosim/jupyterhub-base:$BASE_IMAGE

LABEL maintainer="James Gebbie-Rayet <james.gebbie@stfc.ac.uk>"

# Switch to jovyan user.
USER $NB_USER
WORKDIR $HOME

# Install workshop deps
RUN conda install -y -c conda-forge \
conda-forge::openfe=1.12 \
conda-forge::nglview=4.0.1 \
conda-forge::ipywidgets=8.1.9

COPY docker/fix-nglview.sh /tmp/fix-nglview.sh
RUN bash /tmp/fix-nglview.sh

# Get workshop files and move them to jovyan directory.
COPY --chown=1000:100 . .
RUN rm -rf LICENSE README.md docker .git .github environment.yml

# Copy lab workspace
#COPY --chown=1000:100 docker/default-37a8.jupyterlab-workspace /home/jovyan/.jupyter/lab/workspaces/default-37a8.jupyterlab-workspace

# Always finish with non-root user as a precaution.
USER $NB_USER
36 changes: 36 additions & 0 deletions docker/fix-nglview.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail

# Locate nglview's Python frontend file
NGLVIEW_DIR=$(python -c "import nglview, os; print(os.path.dirname(nglview.__file__))")
FRONTEND_FILE="$NGLVIEW_DIR/_frontend.py"

if [[ ! -f "$FRONTEND_FILE" ]]; then
echo "nglview _frontend.py not found, skipping patch" >&2
exit 0
fi

# Locate the installed labextension's package.json by searching
# every labextensions directory Jupyter knows about (handles conda's
# $CONDA_PREFIX/share/jupyter/labextensions layout automatically)
LABEXT_PKG=$(python -c "
from jupyter_core.paths import jupyter_path
import os, sys
for d in jupyter_path('labextensions'):
candidate = os.path.join(d, 'nglview-js-widgets', 'package.json')
if os.path.isfile(candidate):
print(candidate)
sys.exit(0)
sys.exit(1)
") || { echo "nglview-js-widgets labextension not found, skipping patch" >&2; exit 0; }

PY_VERSION=$(python -c "import re; print(re.search(r\"__frontend_version__ = '([^']+)'\", open('$FRONTEND_FILE').read()).group(1))")
JS_VERSION=$(python -c "import json; print(json.load(open('$LABEXT_PKG'))['version'])")

# If there is a version mismatch then make them the same python side.
if [[ "$PY_VERSION" != "$JS_VERSION" ]]; then
echo "nglview mismatch: python=$PY_VERSION js=$JS_VERSION (found at $LABEXT_PKG) -- patching"
sed -i "s/__frontend_version__ = '$PY_VERSION'/__frontend_version__ = '$JS_VERSION'/" "$FRONTEND_FILE"
else
echo "nglview versions already match ($PY_VERSION), skipping patch"
fi