From ab7115a0840fd9ab152966f525db34c7223ec7c5 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:21:47 +0100 Subject: [PATCH 1/3] migrate CI workflow to repo --- .github/dependabot.yml | 16 ++ .github/workflows/build.yaml | 184 +++++++++++++++++++++++ README.md | 20 ++- docker/Dockerfile | 28 ++++ docker/default-37a8.jupyterlab-workspace | 1 + docker/jupyter_notebook_config.py | 2 + 6 files changed, 245 insertions(+), 6 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yaml 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/README.md b/README.md index b7939b7..b41efeb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ -# basic-statistics-workshop +# CCPBioSim Basic Statistics Workshop -This workshop illustrates how basic statistical concepts can be applied to the analysis of biomolecular simlation data. Using as an example some of the data from [Shaw et al.'s millisecond MD simulation of BPTI](http://science.sciencemag.org/content/330/6002/341.full), we investigate a series of increasingly rigorous statistical approaches to answering a simple question: what is the average distance between the N- and C-terminal amino acids in the simulation? +[![build](https://github.com/ccpbiosim/basic-statistics-workshop/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/ccpbiosim/basic-statistics-workshop/actions/workflows/build.yaml) -## Requirements -A basic knowledge of Python and MD simulation methods. +## Docker -## Training material -A Jupyter notebook and associated data files. +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-statistics-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-statistics-workshop:latest ## Contact Please direct all enquiries and comments to [Charlie Laughton](mailto:charles.laughton@nottingham.ac.uk) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6b02b31 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,28 @@ +# 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/basic-statistics-workshop +LABEL org.opencontainers.image.description="A container environment for the ccpbiosim workshop on basic statistics." +LABEL org.opencontainers.image.licenses=MIT + +# Switch to jovyan user. +USER $NB_USER +WORKDIR $HOME + +# Install workshop deps +RUN conda install matplotlib numpy ipywidgets -y + +# 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..76b6d20 --- /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 statistics in data analysis.ipynb"]},"current":"notebook:Basic statistics in data 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 statistics in data analysis.ipynb":{"data":{"path":"Basic statistics in data 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 cdeb1afcea4daab9898b04e264556f002f3796e0 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:23:08 +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 80faa348a9de56326da40a1e554632d320017a66 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Thu, 18 Sep 2025 16:39:02 +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 6b02b31..008b45a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,7 +16,7 @@ RUN conda install matplotlib numpy ipywidgets -y # 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