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
143 changes: 143 additions & 0 deletions .github/workflows/shapemapper2-container-poc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: ShapeMapper2 candidate build and private publish

on:
pull_request:
paths:
- .github/workflows/shapemapper2-container-poc.yml
- containers/shapemapper2/**
workflow_dispatch:
inputs:
publish_private:
description: Push a uniquely tagged private development image after tests pass
required: true
type: boolean
default: false

permissions:
contents: read

env:
LOCAL_IMAGE: nerd-shapemapper2:2.3-public-candidate

jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
# LucksLab permits only LucksLab-owned actions. Check out the exact event
# ref directly instead of using actions/checkout.
- name: Check out the exact revision
env:
CHECKOUT_REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
run: |
git init .
git remote add origin "https://github.com/${REPOSITORY}.git"
git fetch --no-tags --depth=1 origin "${CHECKOUT_REF}"
git checkout --detach FETCH_HEAD

- name: Build amd64 image without publishing
run: |
docker build \
--platform linux/amd64 \
--tag "${LOCAL_IMAGE}" \
containers/shapemapper2

- name: Confirm required runtime commands
run: |
docker run --rm \
--platform linux/amd64 \
--entrypoint bash \
"${LOCAL_IMAGE}" \
-c '
set -e
for tool in bbmerge.sh bowtie2 bowtie2-build STAR dot gs pv python python3; do
command -v "${tool}"
done
dpkg-query -W bbmap-jni
python3 -c "import matplotlib, numpy, scipy, sklearn"
'

- name: Run upstream example smoke test
run: containers/shapemapper2/test-container.sh "${LOCAL_IMAGE}"

- name: Run complete upstream test suite
run: containers/shapemapper2/test-upstream.sh "${LOCAL_IMAGE}"

- name: Record image size and Debian package inventory
run: |
docker run --rm --entrypoint dpkg-query \
"${LOCAL_IMAGE}" \
-W '-f=${binary:Package}\t${Version}\t${source:Package}\t${source:Version}\n' \
| LC_ALL=C sort \
| tee shapemapper2-2.3-r0.debian-packages.tsv

image_size_bytes="$(docker image inspect "${LOCAL_IMAGE}" --format '{{.Size}}')"
package_count="$(wc -l < shapemapper2-2.3-r0.debian-packages.tsv | tr -d ' ')"
{
echo '### ShapeMapper2 candidate'
echo "- Image size: ${image_size_bytes} bytes"
echo "- Installed Debian packages: ${package_count}"
echo "- Source revision: ${GITHUB_SHA}"
} >> "${GITHUB_STEP_SUMMARY}"

- name: Push uniquely tagged private development image
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_private }}
env:
GHCR_TOKEN: ${{ secrets.EDR_CHOI_GHCR_TOKEN }}
run: |
set -euo pipefail
test -n "${GHCR_TOKEN}"

package_api_url='https://api.github.com/users/edr-choi/packages/container/nerd-shapemapper2'
status_code="$(curl --silent --show-error \
--output package-before.json \
--write-out '%{http_code}' \
--header "Authorization: Bearer ${GHCR_TOKEN}" \
--header 'Accept: application/vnd.github+json' \
--header 'X-GitHub-Api-Version: 2022-11-28' \
"${package_api_url}")"

if [[ "${status_code}" == '200' ]]; then
test "$(jq -r '.visibility' package-before.json)" == 'private'
elif [[ "${status_code}" != '404' ]]; then
echo "Unable to confirm the existing package visibility (HTTP ${status_code})" >&2
exit 1
fi

printf '%s' "${GHCR_TOKEN}" \
| docker login ghcr.io --username edr-choi --password-stdin
trap 'docker logout ghcr.io >/dev/null 2>&1 || true' EXIT

short_sha="${GITHUB_SHA:0:12}"
image_ref="ghcr.io/edr-choi/nerd-shapemapper2:2.3-dev-${short_sha}-run${GITHUB_RUN_ID}"
docker tag "${LOCAL_IMAGE}" "${image_ref}"
docker push "${image_ref}" 2>&1 | tee docker-push.log

digest="$(sed -n 's/^.*digest: \(sha256:[0-9a-f]\{64\}\).*$/\1/p' docker-push.log | tail -1)"
test -n "${digest}"

for attempt in 1 2 3 4 5; do
status_code="$(curl --silent --show-error \
--output package-after.json \
--write-out '%{http_code}' \
--header "Authorization: Bearer ${GHCR_TOKEN}" \
--header 'Accept: application/vnd.github+json' \
--header 'X-GitHub-Api-Version: 2022-11-28' \
"${package_api_url}")"
[[ "${status_code}" == '200' ]] && break
sleep 3
done
test "${status_code}" == '200'
test "$(jq -r '.visibility' package-after.json)" == 'private'

digest_ref="ghcr.io/edr-choi/nerd-shapemapper2@${digest}"
{
echo '### Private development image'
echo "- Tag: \`${image_ref}\`"
echo "- Digest-pinned reference: \`${digest_ref}\`"
echo '- Visibility confirmed: `private`'
} >> "${GITHUB_STEP_SUMMARY}"

echo "Private image: ${image_ref}"
echo "Digest-pinned reference: ${digest_ref}"
14 changes: 14 additions & 0 deletions containers/shapemapper2/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
**
!Dockerfile
!Dockerfile.reference
!LICENSES/
!LICENSES/**
!THIRD_PARTY_NOTICES.md
!PUBLIC_THIRD_PARTY_NOTICES.md
!patches/
!patches/bbmerge-stdin-format.patch
!patches/numpy-removed-aliases.patch
!patches/python311-open-mode.patch
!source-manifest.tsv
!conda-packages.tsv
!copyleft-conda-packages.tsv
136 changes: 136 additions & 0 deletions containers/shapemapper2/COMPLIANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# ShapeMapper2 container compliance checklist

This is a working redistribution checklist, not legal advice. Public GHCR
publication remains blocked until the public-candidate checklist is complete
and the project owner gives explicit approval.

## Public candidate (`Dockerfile`)

The public candidate compiles the checksum-pinned upstream v2.3 source tag,
after applying three documented runtime compatibility patches, and
uses Debian 12 packages. It excludes the official release archive's Miniconda
environment and old prebuilt third-party executables. That removes the exact
Azul Zulu and historical Conda source gaps described later in this document.

- [x] Pin the upstream ShapeMapper v2.3 source archive by SHA-256.
- [x] Preserve the ShapeMapper MIT notice in the source tree and image.
- [x] Apply only the documented `python311-open-mode.patch` (`rU` to `r` in
30 file-open calls across 18 files); do not change scientific logic or
vendor/fork the upstream repository.
- [x] Apply only the documented `bbmerge-stdin-format.patch` (`in=stdin` to
`in=stdin.fastq`) so Debian BBMap 39.01 pairs the existing FASTQ stream; do
not change BBMerge analysis parameters.
- [x] Apply only the documented `numpy-removed-aliases.patch` (`np.int` to
equivalent built-in `int` in four array allocations); do not change array
values or scientific logic.
- [x] Use distribution packages for Bowtie2, STAR, BBMap/BBMerge and its
separately packaged JNI library, Graphviz, Ghostscript, pv, OpenJDK, Python,
Debian's Python-to-Python-3 command compatibility link, Boost, zlib, and
transitive dependencies.
- [x] Preserve Debian package copyright records under
`/usr/share/doc/<package>/copyright`.
- [x] Add `PUBLIC_THIRD_PARTY_NOTICES.md` as an index to those records.
- [ ] Run the upstream example and all built-in tests against the final
candidate image.
- [ ] Export the exact installed binary-to-source Debian package inventory
using `dpkg-query`, and retain it with the release record.
- [ ] Generate a fresh SPDX SBOM from the final candidate; the existing
`sbom.spdx.json` describes only the private reference image.
- [ ] Retrieve and retain the exact Debian corresponding-source packages needed
for redistributed copyleft components. Do this from the tested final package
inventory, not from the older Conda list.
- [ ] Confirm that every directly used executable is found at the expected
command name, especially Debian's BBMerge launcher.
- [ ] Record the final image digest, CI run, package inventory, SBOM, and source
bundle checksums together.
- [x] Obtain explicit owner approval before pushing a uniquely tagged private
development image for NERD integration testing.
- [ ] Obtain explicit owner approval before creating a release tag or changing
GHCR visibility to public.

The first local source build produced a 508,942,308-byte amd64 image. Runtime
inspection was interrupted by host disk exhaustion immediately after the
build. A second build compiled with the final two-job limit and installed all
runtime packages, but storage failed during final layer assembly. The size is
therefore provisional evidence rather than a release result.

## Private reference image (`Dockerfile.reference`)

The remaining sections document the official-release wrapper. They explain why
it is retained privately as a behavioral reference rather than published.

## Evidence already captured

- [x] Preserve the upstream ShapeMapper MIT license in the image and in
`LICENSES/ShapeMapper-MIT.txt`.
- [x] Pin and checksum the exact upstream v2.3 release archive.
- [x] Generate an SPDX JSON SBOM from the locally built image.
- [x] Record the prominent bundled tools and their versions.
- [x] Extract and preserve the upstream archive's exact license texts for
Bowtie2, Ghostscript, Miniconda, and pv.
- [x] Extract and preserve the Conda environment's exact license texts for
BBMap, Graphviz, and OpenJDK.
- [x] Export all 218 embedded Conda package records, including exact package
URLs and available checksums, to `conda-packages.tsv`.
- [x] Isolate 64 Conda records with copyleft-related metadata into
`copyleft-conda-packages.tsv` for source/notice follow-up.
- [x] Download and checksum exact source releases for Bowtie2, STAR,
Ghostscript, BBMap, and Graphviz.
- [x] Preserve the exact Conda package archives containing the historical
build recipes for STAR, BBMap, Graphviz, and Azul Zulu OpenJDK in the source
retrieval plan.

The current SBOM contains 168 package records. Thirty-six records have
`NOASSERTION` for at least one SPDX license field, but the Conda metadata
resolves most of those scanner omissions. Four Conda records themselves lack
a license value: `_libgcc_mutex`, `mysql-common`, `mysql-libs`, and the old
`readline` 6.2 package. The SBOM remains discovery evidence rather than a
complete redistribution determination.

## Historical release-wrapper review

- [ ] ShapeMapper2: retain the MIT copyright and permission notice.
- [x] Bowtie2 2.3.4.3: retain its bundled GPLv3 license and map its exact
versioned source archive and build scripts.
- [x] STAR 2.5.2a: retain GPLv3 and map the exact source plus the Conda recipe
that selected the bundled static executable.
- [ ] Ghostscript 9.25: conservatively treat the executable as AGPL and ship
the official complete 9.25 source. Keep documenting that ShapeMapper's
adjacent binary-archive `COPYING` file unexpectedly contains GPLv3 instead.
- [x] BBMap/BBMerge 37.78: retain the UC/LBNL notice. The license is permissive,
the exact source was verified, and the installed package includes Java source.
- [x] pv 1.6.20: retain Artistic License 2.0. The bundled tarball is a source
archive; the previous GPL classification was incorrect.
- [x] Graphviz 7.1.0: retain EPL-1.0 and map the exact source, Conda package,
recipe, and patches.
- [ ] OpenJDK 8.0.112: the exact Zulu binary input, license, exception, third-
party notices, source-offer identifier, Conda package, and recipe are known.
The upstream OpenJDK 8u112-b16 source is only a baseline; obtain Azul's
complete source for build `Zulu 8.19.0.1 d0cf8daf3adb` or replace this JDK
with a source-traceable runtime before public distribution.
- [ ] Miniconda and Conda packages: retain Miniconda terms; resolve the four
empty license records and map corresponding source for every remaining
copyleft package, not only the prominent tools. The focused list currently
contains 64 records, including large build/GUI stacks that ShapeMapper may
not need at runtime.
- [ ] Debian base packages: retain required copyright/license material and
document the Debian source retrieval path for the exact package versions.
- [x] Assemble a draft distributable `THIRD_PARTY_NOTICES.md`, license set,
source manifest, and checksum-verifying source fetcher.
- [ ] Retain the verified source collection as immutable release artifacts;
do not rely solely on upstream URLs.
- [ ] Optionally have the final notices/source bundle reviewed before changing
GHCR visibility to public.

## Publication records for the public candidate

- [ ] Save the successful CI run URL and source commit.
- [ ] Save the pushed OCI digest and generated provenance attestation.
- [ ] Attach the SPDX SBOM to the immutable release record.
- [ ] Validate the digest-pinned image after conversion to SIF on Quest.
- [ ] Record the SIF SHA-256, runtime/module version, host, date, and test log.
- [ ] Obtain explicit owner approval before making the GHCR package public.
- [ ] Verify anonymous pull only after approval and visibility change.

Do not describe the container as compliance-complete while any review item is
unchecked.
99 changes: 99 additions & 0 deletions containers/shapemapper2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# syntax=docker/dockerfile:1.7

# Public candidate: compile the checksum-pinned upstream v2.3 source tag with
# three documented runtime compatibility patches, and use
# source-traceable Debian packages for runtime dependencies.
FROM --platform=linux/amd64 debian:bookworm-slim@sha256:362e64223cc0da95422b3b13c045186fc0a81250e765d31c025fbddf257f6143 AS build

ARG SHAPEMAPPER_VERSION=2.3
ARG SHAPEMAPPER_SOURCE_SHA256=1c1fb1e6e03249715eedb822217c8c3b5d18a0d60724ca4d403d88f29f866b6f
ARG SHAPEMAPPER_SOURCE_URL=https://codeload.github.com/Weeks-UNC/shapemapper2/tar.gz/refs/tags/v2.3

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
cmake \
g++ \
libboost-filesystem-dev \
libboost-iostreams-dev \
libboost-program-options-dev \
libboost-system-dev \
make \
patch \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*

ADD --checksum=sha256:1c1fb1e6e03249715eedb822217c8c3b5d18a0d60724ca4d403d88f29f866b6f \
https://codeload.github.com/Weeks-UNC/shapemapper2/tar.gz/refs/tags/v2.3 \
/tmp/shapemapper2-v2.3.tar.gz

COPY patches/python311-open-mode.patch /tmp/python311-open-mode.patch
COPY patches/bbmerge-stdin-format.patch /tmp/bbmerge-stdin-format.patch
COPY patches/numpy-removed-aliases.patch /tmp/numpy-removed-aliases.patch

RUN test "${SHAPEMAPPER_VERSION}" = "2.3" \
&& test "${SHAPEMAPPER_SOURCE_SHA256}" = "1c1fb1e6e03249715eedb822217c8c3b5d18a0d60724ca4d403d88f29f866b6f" \
&& test "${SHAPEMAPPER_SOURCE_URL}" = "https://codeload.github.com/Weeks-UNC/shapemapper2/tar.gz/refs/tags/v2.3" \
&& tar -xzf /tmp/shapemapper2-v2.3.tar.gz -C /opt \
&& mv /opt/shapemapper2-2.3 /opt/shapemapper2 \
&& rm /tmp/shapemapper2-v2.3.tar.gz \
&& patch --batch --forward --strip=2 --directory=/opt/shapemapper2 < /tmp/python311-open-mode.patch \
&& patch --batch --forward --strip=2 --directory=/opt/shapemapper2 < /tmp/bbmerge-stdin-format.patch \
&& patch --batch --forward --strip=2 --directory=/opt/shapemapper2 < /tmp/numpy-removed-aliases.patch \
&& rm /tmp/python311-open-mode.patch /tmp/bbmerge-stdin-format.patch /tmp/numpy-removed-aliases.patch \
&& cmake -S /opt/shapemapper2 -B /tmp/shapemapper-build -DCMAKE_BUILD_TYPE=Release \
&& cmake --build /tmp/shapemapper-build --parallel 2

FROM --platform=linux/amd64 debian:bookworm-slim@sha256:362e64223cc0da95422b3b13c045186fc0a81250e765d31c025fbddf257f6143

ARG SHAPEMAPPER_VERSION=2.3
ARG SHAPEMAPPER_SOURCE_SHA256=1c1fb1e6e03249715eedb822217c8c3b5d18a0d60724ca4d403d88f29f866b6f

LABEL org.opencontainers.image.title="NERD ShapeMapper2" \
org.opencontainers.image.description="ShapeMapper2 v2.3 built from its checksum-pinned upstream source tag with documented runtime compatibility patches" \
org.opencontainers.image.source="https://github.com/LucksLab/nerd" \
org.opencontainers.image.url="https://github.com/LucksLab/nerd" \
org.opencontainers.image.version="2.3-r0-candidate" \
org.opencontainers.image.vendor="Lucks Lab" \
org.opencontainers.image.documentation="https://github.com/LucksLab/nerd/tree/main/containers/shapemapper2" \
io.nerd.shapemapper.upstream="https://github.com/Weeks-UNC/shapemapper2/tree/v2.3" \
io.nerd.shapemapper.source-sha256="1c1fb1e6e03249715eedb822217c8c3b5d18a0d60724ca4d403d88f29f866b6f" \
io.nerd.shapemapper.distribution="public-candidate"

# Keep /usr/share/doc: Debian copyright files are part of the redistribution
# record and intentionally are not removed as a size optimization.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bbmap \
bbmap-jni \
bowtie2 \
ca-certificates \
fonts-dejavu-core \
ghostscript \
graphviz \
libboost-filesystem1.74.0 \
libboost-iostreams1.74.0 \
libboost-program-options1.74.0 \
libboost-system1.74.0 \
pv \
python3 \
python-is-python3 \
python3-matplotlib \
python3-numpy \
python3-scipy \
python3-sklearn \
rna-star \
zlib1g \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /opt/shapemapper2 /opt/shapemapper2
COPY LICENSES/ShapeMapper-MIT.txt /usr/share/licenses/nerd-shapemapper2/ShapeMapper-MIT.txt
COPY PUBLIC_THIRD_PARTY_NOTICES.md /usr/share/licenses/nerd-shapemapper2/THIRD_PARTY_NOTICES.md

ENV SHAPEMAPPER_ROOT_DIR=/opt/shapemapper2 \
PATH=/opt/shapemapper2:/opt/shapemapper2/internals/bin:/usr/share/bbmap:${PATH}

WORKDIR /work

ENTRYPOINT ["/opt/shapemapper2/shapemapper"]
CMD ["--help"]
Loading