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
43 changes: 38 additions & 5 deletions .github/workflows/publish-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
paths:
- "**/Dockerfile"
- "images/druid/complete/**"
- ".github/workflows/publish-ghcr.yml"
workflow_dispatch:
inputs:
dockerfile:
Expand Down Expand Up @@ -42,27 +44,32 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
BEFORE_SHA: ${{ github.event.before }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
DOCKERFILE: ${{ inputs.dockerfile }}
REQUESTED_DOCKERFILE: ${{ inputs.dockerfile }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_SHA: ${{ github.sha }}
run: |
set -euo pipefail

if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
changed_files="$DOCKERFILE"
changed_files="$REQUESTED_DOCKERFILE"
elif [[ "$BEFORE_SHA" =~ ^0+$ ]]; then
# A branch's first push has no predecessor SHA. Compare it with
# the default branch instead of treating every repository file as
# newly added.
base_sha="$(git merge-base "origin/$DEFAULT_BRANCH" "$GITHUB_SHA")"
changed_files="$(git diff --name-only --diff-filter=AMR "$base_sha" "$GITHUB_SHA")"
changed_files="$(git diff --name-only --diff-filter=AMRD "$base_sha" "$GITHUB_SHA")"
else
changed_files="$(git diff --name-only --diff-filter=AMR "$BEFORE_SHA" "$GITHUB_SHA")"
changed_files="$(git diff --name-only --diff-filter=AMRD "$BEFORE_SHA" "$GITHUB_SHA")"
fi

mapfile -t dockerfiles < <(
printf '%s\n' "$changed_files" | awk '/(^|\/)Dockerfile$/'
printf '%s\n' "$changed_files" | awk '
/^images\/druid\/complete\// || $0 == ".github/workflows/publish-ghcr.yml" {
print "images/druid/complete/Dockerfile"; next
}
/(^|\/)Dockerfile$/ { print }
' | sort -u
)

if (( ${#dockerfiles[@]} == 0 )); then
Expand Down Expand Up @@ -101,6 +108,18 @@ jobs:
image_tag="$(printf '%s' "$image_tag" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')"
image="ghcr.io/$owner/$image_name"

if [[ "$context" == "images/druid/complete" ]]; then
# Test the same cached build before publishing either GHCR tag.
docker buildx build --platform linux/amd64 --load \
--file "$dockerfile" --tag druid:ci "$context"
mkdir -p "$RUNNER_TEMP/druid-test"
docker run --rm --platform linux/amd64 \
--user "$(id -u):$(id -g)" \
--volume "$RUNNER_TEMP/druid-test:/work" \
druid:ci druid smoke-test --work /work \
2>&1 | tee "$RUNNER_TEMP/druid-test.log"
fi

echo "Publishing $dockerfile as $image:$image_tag and $image:sha-$revision"
docker buildx build \
--file "$dockerfile" \
Expand All @@ -114,3 +133,17 @@ jobs:
--push \
"$context"
done

- name: Upload DRUID validation logs and results
if: always()
uses: actions/upload-artifact@v4
with:
name: druid-validation-${{ github.sha }}
if-no-files-found: ignore
retention-days: 14
path: |
${{ runner.temp }}/druid-test.log
${{ runner.temp }}/druid-test/**/Analysis/HEK293_R1/*.csv
${{ runner.temp }}/druid-test/**/Analysis/HEK293_R1/*.pdf
${{ runner.temp }}/druid-test/**/Analysis/HEK293_R1/*provenance.json
${{ runner.temp }}/druid-test/**/Analysis/HEK293_R1/*Info.txt
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Fixed

- Run DRUID CI validation with the GitHub Actions runner's UID/GID so its
root-owned smoke-test directory no longer prevents artifact upload after a
successful image publication.
- Pinned the Bento Tools 2.1.3 base image to RAPIDS Singlecell 0.10.9 so its
legacy CUDA 12.6 dependency set is not broken by changes to the `latest` tag.
- Updated the Bento Tools 2.1.3 image to cryptography 49.0.0 and its required
Expand All @@ -17,5 +20,14 @@

### Added

- Singularity-first DRUID deployment and tutorial instructions for Dockerless
clusters, including the tested `sha-7941ba6aebf0fd4beb48643ec373374b50b02bbc`
image pull command.
- DRUID complete-analysis container at `images/druid/complete`, including
verified GEO/ENA sample mappings, compatibility fixes, and full workflow tests.
- DRUID builds triggered by supporting-file changes; GitHub Actions tests the
container before publishing `ghcr.io/yeolab/druid:complete` and a commit tag,
and retains validation artifacts for 14 days.

- GitHub Actions publishing of changed Dockerfiles to GitHub Container Registry
(GHCR), including source, SBOM, and provenance metadata.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,31 @@ apptainer pull fastp_0.23.3.sif docker://ghcr.io/yeolab/fastp:sha-<full-commit-s

Set the GHCR package to public from GitHub's **Packages** page if users should
be able to pull it without credentials.

## DRUID complete analysis

The [DRUID build context](images/druid/complete/) includes the software,
GEO/ENA sample manifest, full-analysis runner, and synthetic end-to-end tests.
Changes to any file in `images/druid/complete/` trigger publishing, including
changes to scripts, tests, and configuration. The publishing workflow itself
also triggers a DRUID rebuild. CI builds for `linux/amd64`, runs the complete
synthetic analysis and reference-converter checks, and publishes only after
those checks pass. Validation logs, half-life tables, heatmaps, and provenance
are retained as Actions artifacts for 14 days.

```bash
singularity pull druid.sif docker://ghcr.io/yeolab/druid:sha-7941ba6aebf0fd4beb48643ec373374b50b02bbc
export DRUID_IMAGE="$PWD/druid.sif"
mkdir -p "$HOME/DRUID/tmp" "$HOME/DRUID/logs"
cd "$HOME/DRUID"
singularity exec --cleanenv --env THREADS=8,TMPDIR=/work/tmp \
--bind "$PWD:/work" --pwd /work "$DRUID_IMAGE" druid all \
2>&1 | tee logs/full-analysis.log
```

Run on an x86-64 cluster compute node with at least 32 GB RAM (40 GB
recommended) and eight CPUs. The CI test uses a small synthetic reference and
does not download or analyze the full GEO experiment. See the image's
[README](images/druid/complete/README.md) for scheduler guidance and separate
download, reference, analysis, and fitting commands. The image is also tagged
`ghcr.io/yeolab/druid:sha-<full-commit-sha>`.
9 changes: 9 additions & 0 deletions images/druid/complete/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*
!Dockerfile
!scripts/
!scripts/**
!tests/
!tests/**
!config/
!config/**
**/__pycache__
12 changes: 12 additions & 0 deletions images/druid/complete/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.DS_Store
upstream/
reference/
vendor/
logs/
work/
tmp/
ExternalData/
RawSequencingData/
Analysis/
__pycache__/
*.pyc
15 changes: 15 additions & 0 deletions images/druid/complete/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

## 2026-09-08

- Document Singularity-first deployment and tutorial execution on Dockerless
clusters using the tested `sha-7941ba6aebf0fd4beb48643ec373374b50b02bbc` image.

- Add GitHub Actions publication to `ghcr.io/yeolab/druid:complete` after end-to-end validation, with rebuilds triggered by supporting-file changes.

- Add an amd64 Dockerfile and Compose service for DRUID's complete-analysis workflow.
- Add verified GEO/ENA mappings and resumable, checksum-verified FASTQ downloads for HEK293 replicate 1.
- Automate combined-reference construction, QC, trimming, alignment, exon/intron quantification, and both half-life analyses.
- Update the GenomicFeatures selection API, port coverage calculation to Python 3, fix exact unique-read filtering, and correct DRUID normalization dimensions while retaining upstream originals.
- Add a synthetic end-to-end test, known-half-life and sequencing-depth-invariance checks, and coverage-statistics checks.
- Document resource requirements, compatibility changes, provenance, and limits of validation.
47 changes: 47 additions & 0 deletions images/druid/complete/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1
# UCSC's distributed tools are x86-64. Build with --platform linux/amd64.
FROM debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171
ARG TARGETARCH
ARG DRUID_COMMIT=455be347cab87b62a2d1f081ec34cafffdf81622
ENV DEBIAN_FRONTEND=noninteractive LC_ALL=C.UTF-8 LANG=C.UTF-8 \
DRUID_HOME=/opt/DRUID PATH=/opt/DRUID:/opt/DRUID/Scripts:/opt/workflow:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
THREADS=8 TMPDIR=/work/tmp
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN test "$TARGETARCH" = amd64 || { echo 'Build with --platform linux/amd64'; exit 1; }
RUN mkdir -p /work/tmp && apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl wget git perl bash coreutils gawk parallel procps \
samtools bedtools rna-star fastqc trimmomatic default-jre-headless \
python3 python3-numpy python3-htseq python-is-python3 sra-toolkit \
r-base-core r-cran-gplots r-cran-plyr r-bioc-genomicfeatures r-bioc-rtracklayer \
libmariadb3 libpng16-16 libssl3 libuuid1 libgomp1 \
&& dpkg-query -W > /opt/debian-packages.tsv \
&& rm -rf /var/lib/apt/lists/*
RUN git init /opt/DRUID && cd /opt/DRUID \
&& git remote add origin https://github.com/risslandlab/DRUID.git \
&& git fetch --depth 1 origin "$DRUID_COMMIT" && git checkout --detach FETCH_HEAD \
&& test "$(git rev-parse HEAD)" = "$DRUID_COMMIT" \
&& echo "$DRUID_COMMIT" > /opt/DRUID/UPSTREAM_COMMIT && rm -rf .git
# Checksums intentionally fail if UCSC replaces these unversioned downloads.
RUN curl -fL --retry 5 https://hgdownload.soe.ucsc.edu/admin/exe/linux.x86_64/twoBitToFa -o /usr/local/bin/twoBitToFa \
&& curl -fL --retry 5 https://hgdownload.soe.ucsc.edu/admin/exe/linux.x86_64/genePredToGtf -o /usr/local/bin/genePredToGtf \
&& echo '10b2cd0d1f8208e8c0e9990b4a97d90cda4eec754b13930a10d55a40bda56710 /usr/local/bin/twoBitToFa' | sha256sum -c - \
&& echo '3596b456da9c423686a2a3f01edab9915d1969144601c0aac3c8b694375a2c18 /usr/local/bin/genePredToGtf' | sha256sum -c - \
&& chmod 755 /usr/local/bin/twoBitToFa /usr/local/bin/genePredToGtf
COPY scripts/ /opt/workflow/
COPY tests/ /opt/tests/
COPY config/ /opt/config/
RUN python3 /opt/workflow/patch_upstream.py \
&& chmod +x /opt/DRUID/*.sh /opt/DRUID/*.py /opt/DRUID/Scripts/* /opt/workflow/*.sh \
&& ln -s /usr/share/java/trimmomatic.jar /opt/trimmomatic.jar \
&& ln -s /usr/share/trimmomatic /opt/trimmomatic \
&& ln -s /opt/workflow/druid.py /usr/local/bin/druid \
&& chmod +x /opt/workflow/druid.py \
&& Rscript -e 'library(GenomicFeatures); library(rtracklayer); library(plyr); library(gplots); source("/opt/DRUID/DRUID.R"); stopifnot(is.function(DRUID))' \
&& python3 -c 'import numpy, HTSeq' \
&& STAR --version && samtools --version && bedtools --version && fastqc --version \
&& mkdir -p /work/tmp
LABEL org.opencontainers.image.title="DRUID complete analysis" \
org.opencontainers.image.source="https://github.com/risslandlab/DRUID" \
org.opencontainers.image.revision="455be347cab87b62a2d1f081ec34cafffdf81622"
WORKDIR /work
CMD ["bash"]
21 changes: 21 additions & 0 deletions images/druid/complete/LICENSE-DRUID
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 risslandlab

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading