From 52fc462ff6b42365b561613f4f916f4dab6e3d7a Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:20:39 +0100 Subject: [PATCH 1/3] migrate CI build workflow to repo --- .github/dependabot.yml | 16 ++ .github/workflows/build.yaml | 184 +++++++++++++++++++++++ Dockerfile | 37 ----- README.md | 21 ++- _config.yml | 3 - docker/Dockerfile | 29 ++++ docker/default-37a8.jupyterlab-workspace | 1 + docker/jupyter_notebook_config.py | 2 + 8 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yaml delete mode 100644 Dockerfile delete mode 100644 _config.yml create mode 100644 docker/Dockerfile create mode 100644 docker/default-37a8.jupyterlab-workspace create mode 100644 docker/jupyter_notebook_config.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ef2e5af --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every day + interval: "daily" + time: "09:00" + timezone: "UTC" + assignees: + - "jimboid" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..e8b0f30 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,184 @@ +name: ci/cd +on: + pull_request: + repository_dispatch: + types: [build] + workflow_dispatch: + +jobs: + build: + 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: checkout + uses: actions/checkout@v5.0.0 + + - 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@v5.8.0 + with: + images: ghcr.io/${{ github.repository }} + + - name: Authenticate with GHCR + id: auth + uses: docker/login-action@v3.5.0 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.BUILD_TOKEN}} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3.11.1 + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6.18.0 + with: + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ghcr.io/${{ github.repository }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4.6.2 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-24.04 + name: merge into multiarch manifest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v5.0.0 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Authenticate with GHCR + id: auth + uses: docker/login-action@v3.5.0 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.BUILD_TOKEN}} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3.11.1 + + - name: Metadata + id: meta + uses: docker/metadata-action@v5.8.0 + with: + images: ghcr.io/${{ github.repository }} + tags: dev + + - 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/${{ github.repository }}@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/${{ github.repository }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/${{ github.repository }}:dev + + tests: + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} + name: testing on ${{ matrix.platform }} + timeout-minutes: 360 + needs: + - build + - merge + steps: + + - name: Test notebooks + shell: bash + run: | + docker run -t ghcr.io/${{ github.repository }}:dev bash -c " \ + pip install pytest nbmake; \ + find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600; " + + tags: + runs-on: ubuntu-24.04 + if: github.event_name != 'pull_request' + name: add tags + needs: + - build + - tests + steps: + - name: Authenticate with GHCR + id: auth + uses: docker/login-action@v3.5.0 + with: + registry: "ghcr.io" + username: ${{github.actor}} + password: ${{secrets.BUILD_TOKEN}} + + - name: tag release versions + shell: bash + run: | + docker buildx imagetools create \ + --tag ghcr.io/${{ github.repository }}:latest \ + --tag ghcr.io/${{ github.repository }}:${{ needs.build.outputs.tag }} \ + ghcr.io/${{ github.repository }}:dev + + - name: Post version update to dash + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.BUILD_TOKEN }} + repository: jimboid/biosim-workshops-dash + event-type: build + client-payload: '{"repo": "${{ github.event.repository.name }}", "tag": "${{ needs.build.outputs.tag }}"}' diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c50fdfc..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# This is the container for the CCPBioSim basic-analysis-workshop - -# Start with ccpbiosimbase. -FROM ccpbiosim/ccpbiosimbase:latest - -LABEL maintainer="James Gebbie-Rayet " - -# Root to install "rooty" things -USER root - -# Update and Install c-shell. -RUN apt-get update -y -RUN apt-get install -y csh - -# Back to jovyan user -USER $NB_USER - -# Python Dependencies for the md_workshop -RUN pip install --user numpy -RUN pip install --user cython -RUN pip install --user scipy -RUN pip install --user pandas -RUN pip install --user matplotlib -RUN pip install --user mdtraj -RUN pip install --user ipywidgets -RUN pip install --user nglview - -# Initialise NGLView. -RUN jupyter-nbextension install nglview --py --sys-prefix && \ - jupyter-nbextension enable nglview --py --sys-prefix - -# Add all of the workshop files to the home directory -ADD --chown=jovyan:100 *.ipynb $HOME/ -ADD --chown=jovyan:100 data $HOME/data - -# Always finish with non-root user as a precaution. -USER $NB_USER diff --git a/README.md b/README.md index da1e3b8..1212c86 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@ -# CCP-BioSim Training Course: Basic MD data analysis with MDTraj -IPython (Jupyter) notebooks are a great environment in which to do exploratory analysis of MD data. This workshop illustrates how two key Python packages - *numpy* and *matplotlib* - can be used with the MD analysis package [mdtraj](http://mdtraj.org/1.9.3/) for some simple standard analysis tasks. +# CCPBioSim Basic Analysis Workshop -## Requirements -A basic knowledge of Python and MD simulation methods. +[![build](https://github.com/ccpbiosim/basic-analysis-workshop/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/ccpbiosim/basic-analysis-workshop/actions/workflows/build.yaml) -## Training material -A Jupyter notebook and example data files. +## Docker + +This container is derived from the CCPBioSim JupyterHub image. This container +adds the necessary software packages and notebook content to form a deployable +course container. The source content for this course can be found at +https://github.com/CCPBioSim/basic-analysis-workshop + +## How to Use + +In our containers we are using the JupyterHub default port 8888, so you should +forward this port when deploying locally:: + + docker run -p 8888:8888 ghcr.io/jimboid/biosim-basic-analysis-workshop:latest ## Contact Please direct all questions and feedback to [Charlie Laughton](mailto:charles.laughton@nottingham.ac.uk) diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 418b47b..0000000 --- a/_config.yml +++ /dev/null @@ -1,3 +0,0 @@ -theme: jekyll-theme-slate -title: [An Introduction to Basic Interactive Analysis of MD Data] -description: [This workshop shows how Jupyter notebooks and the Python package MDTraj can be used for the interactive analysis of MD trajectory data.] diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..53d174d --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,29 @@ +# Start with BioSim base image. +ARG BASE_IMAGE=latest +FROM ghcr.io/ccpbiosim/jupyterhub-base:$BASE_IMAGE + +LABEL maintainer="James Gebbie-Rayet " +LABEL org.opencontainers.image.source=https://github.com/ccpbiosim/biosim-basic-analysis-workshop +LABEL org.opencontainers.image.description="A container environment for the ccpbiosim workshop on basic analysis." +LABEL org.opencontainers.image.licenses=MIT + +# Switch to jovyan user. +USER $NB_USER +WORKDIR $HOME + +# Install workshop deps +RUN conda install matplotlib numpy nglview ipywidgets -y +RUN pip install mdtraj + +# Get workshop files and move them to jovyan directory. +COPY --chown=1000:100 . . +RUN rm -r AUTHORS LICENSE README.md docker .git .github + +# Copy lab workspace +COPY --chown=1000:100 docker/default-37a8.jupyterlab-workspace /home/jovyan/.jupyter/lab/workspaces/default-37a8.jupyterlab-workspace + +# UNCOMMENT THIS LINE FOR REMOTE DEPLOYMENT +COPY docker/jupyter_notebook_config.py /etc/jupyter/ + +# Always finish with non-root user as a precaution. +USER $NB_USER diff --git a/docker/default-37a8.jupyterlab-workspace b/docker/default-37a8.jupyterlab-workspace new file mode 100644 index 0000000..7304bb5 --- /dev/null +++ b/docker/default-37a8.jupyterlab-workspace @@ -0,0 +1 @@ +{"data":{"layout-restorer:data":{"main":{"dock":{"type":"tab-area","currentIndex":0,"widgets":["notebook:Basic analysis.ipynb"]},"current":"notebook:Basic analysis.ipynb"},"left":{"collapsed":false,"current":"filebrowser","widgets":["filebrowser","running-sessions","@jupyterlab/toc:plugin","extensionmanager.main-view"]},"right":{"collapsed":true,"widgets":["jp-property-inspector"]}},"notebook:Basic analysis.ipynb":{"data":{"path":"Basic analysis.ipynb","factory":"Notebook"}}},"metadata":{"id":"default"}} diff --git a/docker/jupyter_notebook_config.py b/docker/jupyter_notebook_config.py new file mode 100644 index 0000000..a3e5d82 --- /dev/null +++ b/docker/jupyter_notebook_config.py @@ -0,0 +1,2 @@ + +c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator' From 1c6d993861e60b0322f2a1263665377bde172156 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:23:47 +0100 Subject: [PATCH 2/3] fix paths --- .github/workflows/build.yaml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e8b0f30..6845596 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -36,7 +36,7 @@ jobs: id: meta uses: docker/metadata-action@v5.8.0 with: - images: ghcr.io/${{ github.repository }} + images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} - name: Authenticate with GHCR id: auth @@ -54,9 +54,10 @@ jobs: id: build uses: docker/build-push-action@v6.18.0 with: + file: ./docker/Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} - tags: ghcr.io/${{ github.repository }} + tags: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} outputs: type=image,push-by-digest=true,name-canonical=true,push=true - name: Export digest @@ -102,7 +103,7 @@ jobs: id: meta uses: docker/metadata-action@v5.8.0 with: - images: ghcr.io/${{ github.repository }} + images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} tags: dev - name: Create manifest list and push @@ -116,18 +117,18 @@ jobs: --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/${{ github.repository }}@sha256:%s ' *) + $(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/${{ github.repository }}@sha256:%s ' *) + $(printf 'ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@sha256:%s ' *) - name: Inspect image run: | - docker buildx imagetools inspect ghcr.io/${{ github.repository }}:dev + docker buildx imagetools inspect ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:dev tests: strategy: @@ -147,9 +148,9 @@ jobs: - name: Test notebooks shell: bash run: | - docker run -t ghcr.io/${{ github.repository }}:dev bash -c " \ + docker run -t ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:dev bash -c " \ pip install pytest nbmake; \ - find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600; " + find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600;" tags: runs-on: ubuntu-24.04 @@ -171,9 +172,9 @@ jobs: shell: bash run: | docker buildx imagetools create \ - --tag ghcr.io/${{ github.repository }}:latest \ - --tag ghcr.io/${{ github.repository }}:${{ needs.build.outputs.tag }} \ - ghcr.io/${{ github.repository }}:dev + --tag ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:latest \ + --tag ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:${{ needs.build.outputs.tag }} \ + ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:dev - name: Post version update to dash uses: peter-evans/repository-dispatch@v3.0.0 From fddfec44d97bcc3f8b58b05945e62fee10bb8f98 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:38:49 +0100 Subject: [PATCH 3/3] Update Dockerfile --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 53d174d..8de0541 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,7 +17,7 @@ RUN pip install mdtraj # Get workshop files and move them to jovyan directory. COPY --chown=1000:100 . . -RUN rm -r AUTHORS LICENSE README.md docker .git .github +RUN rm -rf AUTHORS LICENSE README.md docker .git .github # Copy lab workspace COPY --chown=1000:100 docker/default-37a8.jupyterlab-workspace /home/jovyan/.jupyter/lab/workspaces/default-37a8.jupyterlab-workspace