scitex-storage 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex_storage-0.2.0/.github/ci/build-in-sif.sh +72 -0
- scitex_storage-0.2.0/.github/ci/exec-in-sif.sh +53 -0
- scitex_storage-0.2.0/.github/ci/publish-in-sif.sh +113 -0
- scitex_storage-0.2.0/.github/ci/run-in-sif.sh +90 -0
- scitex_storage-0.2.0/.github/workflows/ci.yml +73 -0
- scitex_storage-0.2.0/.github/workflows/cla.yml +26 -0
- scitex_storage-0.2.0/.github/workflows/pypi-publish-and-github-release-on-tag.yml +191 -0
- scitex_storage-0.2.0/.gitignore +43 -0
- scitex_storage-0.2.0/.readthedocs.yaml +22 -0
- scitex_storage-0.2.0/.scitex/dev/cli-audit-dict.yaml +10 -0
- scitex_storage-0.2.0/.scitex/dev/config.yaml +4 -0
- scitex_storage-0.2.0/CHANGELOG.md +90 -0
- scitex_storage-0.2.0/CLA.md +80 -0
- scitex_storage-0.2.0/CONTRIBUTING.md +69 -0
- scitex_storage-0.2.0/LICENSE +660 -0
- scitex_storage-0.2.0/PKG-INFO +444 -0
- scitex_storage-0.2.0/README.md +397 -0
- scitex_storage-0.2.0/codecov.yml +23 -0
- scitex_storage-0.2.0/docs/assets/images/scitex-icon-navy-inverted.png +0 -0
- scitex_storage-0.2.0/docs/assets/images/scitex-logo-blue-cropped.png +0 -0
- scitex_storage-0.2.0/docs/sphinx/api/scitex_storage.rst +7 -0
- scitex_storage-0.2.0/docs/sphinx/conf.py +109 -0
- scitex_storage-0.2.0/docs/sphinx/index.rst +29 -0
- scitex_storage-0.2.0/docs/sphinx/installation.rst +23 -0
- scitex_storage-0.2.0/docs/sphinx/quickstart.rst +7 -0
- scitex_storage-0.2.0/docs/sphinx/requirements.txt +5 -0
- scitex_storage-0.2.0/examples/quickstart.py +48 -0
- scitex_storage-0.2.0/pyproject.toml +107 -0
- scitex_storage-0.2.0/src/scitex_storage/__init__.py +123 -0
- scitex_storage-0.2.0/src/scitex_storage/__main__.py +12 -0
- scitex_storage-0.2.0/src/scitex_storage/_archive.py +341 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/__init__.py +136 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_archive_cmd.py +188 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_compat.py +121 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_duplicates_cmd.py +60 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_images_cmd.py +90 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_introspect.py +59 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_mcp_commands.py +62 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_scan_cmd.py +110 -0
- scitex_storage-0.2.0/src/scitex_storage/_cli/_sweep_cmd.py +142 -0
- scitex_storage-0.2.0/src/scitex_storage/_duplicates.py +118 -0
- scitex_storage-0.2.0/src/scitex_storage/_images.py +237 -0
- scitex_storage-0.2.0/src/scitex_storage/_report.py +443 -0
- scitex_storage-0.2.0/src/scitex_storage/_scan.py +324 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_modules/index.html +142 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_modules/scitex_storage/_scan.html +376 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/api/scitex_storage.rst.txt +7 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/index.rst.txt +29 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/installation.rst.txt +23 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_sources/quickstart.rst.txt +7 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/_sphinx_javascript_frameworks_compat.js +123 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/base-stemmer.js +476 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/basic.css +906 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/check-solid.svg +4 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/clipboard.min.js +7 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copy-button.svg +5 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton.css +94 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton.js +248 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/copybutton_funcs.js +73 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/badge_only.css +1 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.svg +2671 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold-italic.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-bold.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal-italic.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/fonts/lato-normal.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/css/theme.css +4 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/doctools.js +150 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/documentation_options.js +13 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/english-stemmer.js +1066 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/file.png +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bold.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-italic.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/Lato/lato-regular.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/jquery.js +2 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/badge_only.js +1 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/theme.js +1 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/js/versions.js +228 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/language_data.js +13 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/minus.png +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/plus.png +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/pygments.css +75 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/searchtools.js +693 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/_static/sphinx_highlight.js +159 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/api/scitex_storage.html +314 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/genindex.html +292 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/index.html +185 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/installation.html +170 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/objects.inv +0 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/py-modindex.html +164 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/quickstart.html +203 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/search.html +161 -0
- scitex_storage-0.2.0/src/scitex_storage/_sphinx_html/searchindex.js +1 -0
- scitex_storage-0.2.0/src/scitex_storage/_sweep.py +384 -0
- scitex_storage-0.2.0/src/scitex_storage/_system_deps.py +91 -0
- scitex_storage-0.2.0/tests/__init__.py +0 -0
- scitex_storage-0.2.0/tests/conftest.py +6 -0
- scitex_storage-0.2.0/tests/develop/__init__.py +7 -0
- scitex_storage-0.2.0/tests/develop/test_audit.py +26 -0
- scitex_storage-0.2.0/tests/examples/__init__.py +0 -0
- scitex_storage-0.2.0/tests/examples/test_quickstart.py +49 -0
- scitex_storage-0.2.0/tests/integration/__init__.py +0 -0
- scitex_storage-0.2.0/tests/integration/test_cross_package_imports.py +91 -0
- scitex_storage-0.2.0/tests/scitex_storage/__init__.py +0 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/__init__.py +0 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test___init__.py +58 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__archive_cmd.py +145 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__compat.py +45 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__duplicates_cmd.py +106 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__images_cmd.py +144 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__introspect.py +34 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__mcp_commands.py +34 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__scan_cmd.py +146 -0
- scitex_storage-0.2.0/tests/scitex_storage/_cli/test__sweep_cmd.py +197 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__archive.py +645 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__duplicates.py +212 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__images.py +309 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__report.py +182 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__scan.py +313 -0
- scitex_storage-0.2.0/tests/scitex_storage/test__sweep.py +432 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs INSIDE the reused scitex-ci SIF (apptainer exec — invoked via
|
|
3
|
+
# exec-in-sif.sh). Builds scitex-storage's wheel + sdist into ./dist/.
|
|
4
|
+
# Adapted from scitex-ssh's .github/ci/build-in-sif.sh (the fleet's
|
|
5
|
+
# established sibling convention) — only the TMPDIR label and echoed package
|
|
6
|
+
# name changed; logic otherwise unchanged.
|
|
7
|
+
#
|
|
8
|
+
# WHY build in the SIF: the self-hosted Spartan runner has no Python on the
|
|
9
|
+
# bare node (the whole reason the old `actions/setup-python@v5` step failed:
|
|
10
|
+
# "version 3.x not found for this OS"). The SIF bakes python 3.11/3.12/3.13 +
|
|
11
|
+
# pip + uv at /opt/venv-<ver>, exactly like the working pytest-matrix CI.
|
|
12
|
+
#
|
|
13
|
+
# `python -m build` needs the `build` frontend, which is NOT baked in the SIF
|
|
14
|
+
# (only scitex-dev[all,dev] deps are). Mirror run-in-sif.sh: install `build`
|
|
15
|
+
# into a writable --target on node-local /tmp and put it on PYTHONPATH. The
|
|
16
|
+
# SIF's /opt/venv-* are root-owned + RO and the compute-node HOME is RO inside
|
|
17
|
+
# the container, so a normal install fails Permission denied — a --target on
|
|
18
|
+
# writable scratch sidesteps both.
|
|
19
|
+
#
|
|
20
|
+
# Fail-loud (operator directive): a missing interpreter or a failed build is a
|
|
21
|
+
# HARD error, never a silent fallback.
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
V="${1:-3.12}"
|
|
25
|
+
VENV="/opt/venv-$V"
|
|
26
|
+
PY="$VENV/bin/python"
|
|
27
|
+
test -x "$PY" || {
|
|
28
|
+
echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export LC_ALL=C.UTF-8 LANG=C.UTF-8
|
|
33
|
+
|
|
34
|
+
# Writable scratch (the runner's TMPDIR=~/.cache/tmp is a host path that does
|
|
35
|
+
# NOT resolve inside the container). Node-local /tmp is writable + ephemeral.
|
|
36
|
+
TMPDIR="/tmp/build-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
|
|
37
|
+
export TMPDIR
|
|
38
|
+
rm -rf "$TMPDIR"
|
|
39
|
+
mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
|
|
40
|
+
|
|
41
|
+
# The compute-node $HOME is RO inside the container — point every cache the
|
|
42
|
+
# installer might touch at the writable scratch (else uv/pip die creating
|
|
43
|
+
# ~/.cache).
|
|
44
|
+
export UV_CACHE_DIR="$TMPDIR/uv-cache"
|
|
45
|
+
export XDG_CACHE_HOME="$TMPDIR"
|
|
46
|
+
export PIP_CACHE_DIR="$TMPDIR/pip-cache"
|
|
47
|
+
|
|
48
|
+
# A VIRTUAL_ENV leaked from the runner profile (~/.env-3.11) is a broken
|
|
49
|
+
# symlink in here; unset it so no tool follows it.
|
|
50
|
+
unset VIRTUAL_ENV || true
|
|
51
|
+
|
|
52
|
+
export PATH="$VENV/bin:$PATH"
|
|
53
|
+
echo "build: py=$("$PY" -V) target=$TMPDIR/site"
|
|
54
|
+
|
|
55
|
+
# Install the PEP 517 build frontend into the writable target (uv fast path,
|
|
56
|
+
# pip safety net), then build with it. Clean dist/ first so only the freshly
|
|
57
|
+
# built artifacts are uploaded.
|
|
58
|
+
uv pip install --python "$PY" --target="$TMPDIR/site" build ||
|
|
59
|
+
"$PY" -m pip install --target="$TMPDIR/site" build
|
|
60
|
+
|
|
61
|
+
export PYTHONPATH="$TMPDIR/site${PYTHONPATH:+:$PYTHONPATH}"
|
|
62
|
+
|
|
63
|
+
rm -rf dist
|
|
64
|
+
"$PY" -m build --outdir dist
|
|
65
|
+
|
|
66
|
+
echo "=== built artifacts ==="
|
|
67
|
+
ls -l dist
|
|
68
|
+
# fail-loud: refuse to continue the pipeline with an empty dist/.
|
|
69
|
+
test -n "$(ls -A dist 2>/dev/null)" || {
|
|
70
|
+
echo "::error::python -m build produced no artifacts in dist/"
|
|
71
|
+
exit 1
|
|
72
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Outer apptainer-exec wrapper for scitex-storage's self-hosted (Spartan) CI.
|
|
3
|
+
# Copied verbatim from scitex-ssh's .github/ci/exec-in-sif.sh (the fleet's
|
|
4
|
+
# established sibling convention) — no package-specific content in this file.
|
|
5
|
+
#
|
|
6
|
+
# Runs ON THE RUNNER (outside the SIF). Resolves the apptainer shim + SIF image
|
|
7
|
+
# from the repo Actions Variables, then `apptainer exec`s the SIF and hands off
|
|
8
|
+
# to an INNER script (run inside the container). Keeps every workflow job's YAML
|
|
9
|
+
# down to one line — `bash .github/ci/exec-in-sif.sh <inner-script> [args...]` —
|
|
10
|
+
# and concentrates all the HPC/SIF plumbing (shim PATH, ~-expansion, scratch,
|
|
11
|
+
# binds) in one version-controlled place.
|
|
12
|
+
#
|
|
13
|
+
# Required env (set by the workflow from repo Actions Variables):
|
|
14
|
+
# SCITEX_CI_APPTAINER path to the apptainer shim (e.g. ~/.env-3.11/bin/apptainer)
|
|
15
|
+
# SCITEX_CI_SIF path to the CI SIF image (e.g. ~/.scitex/dev/containers/ci-cpu.sif)
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# bash .github/ci/exec-in-sif.sh run-in-sif.sh 3.12
|
|
19
|
+
#
|
|
20
|
+
# Fail-loud (operator directive): a missing shim or SIF is a HARD error — never
|
|
21
|
+
# a silent fallback to a bare-runner install.
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
INNER="${1:?inner script name required (relative to .github/ci/)}"
|
|
25
|
+
shift || true
|
|
26
|
+
|
|
27
|
+
# The runner's job shell is --noprofile --norc (no Lmod), so the apptainer shim
|
|
28
|
+
# must be put on PATH explicitly; it execs the real Apptainer binary directly.
|
|
29
|
+
# ~-expand the Actions-Variable paths: a quoted "~/…" is NOT tilde-expanded by
|
|
30
|
+
# the shell, so substitute a leading ~ with $HOME ourselves.
|
|
31
|
+
APPTAINER="${SCITEX_CI_APPTAINER:?SCITEX_CI_APPTAINER not set (repo Actions Variable)}"
|
|
32
|
+
SIF="${SCITEX_CI_SIF:?SCITEX_CI_SIF not set (repo Actions Variable)}"
|
|
33
|
+
APPTAINER="${APPTAINER/#\~/$HOME}"
|
|
34
|
+
SIF="${SIF/#\~/$HOME}"
|
|
35
|
+
export PATH="$HOME/.env-3.11/bin:$PATH"
|
|
36
|
+
|
|
37
|
+
[ -x "$APPTAINER" ] || {
|
|
38
|
+
echo "::error::apptainer shim not executable at $APPTAINER"
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
[ -f "$SIF" ] || {
|
|
42
|
+
echo "::error::CI SIF missing at $SIF — rebuild it: scitex-container apptainer build ci-cpu"
|
|
43
|
+
exit 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# apptainer scratch on the shared FS — keeps HOME clean.
|
|
47
|
+
export APPTAINER_TMPDIR="/data/gpfs/projects/punim0264/ywatanabe/ci/apptainer-tmp"
|
|
48
|
+
mkdir -p "$APPTAINER_TMPDIR"
|
|
49
|
+
|
|
50
|
+
# --bind punim0264: $HOME/.scitex is a symlink into punim0264; bind it so the
|
|
51
|
+
# symlink resolves inside the container. --pwd "$PWD" keeps the checkout as cwd.
|
|
52
|
+
exec "$APPTAINER" exec --pwd "$PWD" --bind /data/gpfs/projects/punim0264 \
|
|
53
|
+
"$SIF" bash ".github/ci/$INNER" "$@"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs INSIDE the reused scitex-ci SIF (apptainer exec — invoked via
|
|
3
|
+
# exec-in-sif.sh). Publishes ./dist/* to PyPI via MANUAL OIDC Trusted
|
|
4
|
+
# Publishing, then twine upload.
|
|
5
|
+
# Adapted from scitex-ssh's .github/ci/publish-in-sif.sh (the fleet's
|
|
6
|
+
# established sibling convention) — only the TMPDIR label and package name in
|
|
7
|
+
# the trust-config comment / final echo changed; logic otherwise unchanged.
|
|
8
|
+
#
|
|
9
|
+
# WHY manual OIDC (not pypa/gh-action-pypi-publish): that action is a Docker
|
|
10
|
+
# container action. Self-hosted Spartan compute nodes have NO Docker, so the
|
|
11
|
+
# action cannot run there. PyPI Trusted Publishing is just an OIDC token
|
|
12
|
+
# exchange over plain HTTPS, so we do it by hand:
|
|
13
|
+
#
|
|
14
|
+
# 1. Ask the GitHub Actions OIDC provider for a JWT with audience=pypi,
|
|
15
|
+
# using the per-job ACTIONS_ID_TOKEN_REQUEST_{TOKEN,URL} env vars (present
|
|
16
|
+
# because the publish job declares `permissions: id-token: write`).
|
|
17
|
+
# apptainer exec (no --cleanenv) passes those host env vars into the SIF.
|
|
18
|
+
# 2. Exchange that JWT at PyPI's mint-token endpoint for a short-lived,
|
|
19
|
+
# scope-limited PyPI API token.
|
|
20
|
+
# 3. twine upload dist/* with TWINE_USERNAME=__token__ and that minted token.
|
|
21
|
+
#
|
|
22
|
+
# REQUIRES a Trusted Publisher to already be configured on PyPI for
|
|
23
|
+
# (project=scitex-storage, owner=scitex-ai, repo=scitex-storage,
|
|
24
|
+
# workflow=release.yml, environment=pypi). This is a ONE-TIME, PyPI-side
|
|
25
|
+
# (pypi.org/manage/) operator action — NOT something this script, or any CI
|
|
26
|
+
# job, can self-configure. If it is not yet registered, mint-token below
|
|
27
|
+
# returns an empty token and this script fails loud with PyPI's raw error body.
|
|
28
|
+
#
|
|
29
|
+
# curl, python and (after a --target install) twine all live in the SIF.
|
|
30
|
+
#
|
|
31
|
+
# Fail-loud (operator directive): every step asserts non-empty output and
|
|
32
|
+
# `set -euo pipefail`; any failure is a HARD error with the exact cause, never
|
|
33
|
+
# a silent skip.
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
|
|
36
|
+
V="${1:-3.12}"
|
|
37
|
+
VENV="/opt/venv-$V"
|
|
38
|
+
PY="$VENV/bin/python"
|
|
39
|
+
test -x "$PY" || {
|
|
40
|
+
echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export LC_ALL=C.UTF-8 LANG=C.UTF-8
|
|
45
|
+
|
|
46
|
+
# dist/ must already hold the artifacts (downloaded by the publish job before
|
|
47
|
+
# this script runs). Fail loud if empty.
|
|
48
|
+
if [ ! -d dist ] || [ -z "$(ls -A dist 2>/dev/null)" ]; then
|
|
49
|
+
echo "::error::dist/ is empty — nothing to publish (download the build artifact first)"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
echo "=== dist to publish ==="
|
|
53
|
+
ls -l dist
|
|
54
|
+
|
|
55
|
+
# --- writable scratch (compute-node HOME is RO inside the container) ---
|
|
56
|
+
TMPDIR="/tmp/publish-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
|
|
57
|
+
export TMPDIR
|
|
58
|
+
rm -rf "$TMPDIR"
|
|
59
|
+
mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
|
|
60
|
+
export UV_CACHE_DIR="$TMPDIR/uv-cache"
|
|
61
|
+
export XDG_CACHE_HOME="$TMPDIR"
|
|
62
|
+
export PIP_CACHE_DIR="$TMPDIR/pip-cache"
|
|
63
|
+
unset VIRTUAL_ENV || true
|
|
64
|
+
export PATH="$VENV/bin:$PATH"
|
|
65
|
+
|
|
66
|
+
# --- step 1: request the OIDC JWT (audience=pypi) from GitHub ---
|
|
67
|
+
: "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:?ACTIONS_ID_TOKEN_REQUEST_TOKEN not set — the publish job needs 'permissions: id-token: write'}"
|
|
68
|
+
: "${ACTIONS_ID_TOKEN_REQUEST_URL:?ACTIONS_ID_TOKEN_REQUEST_URL not set — the publish job needs 'permissions: id-token: write'}"
|
|
69
|
+
|
|
70
|
+
echo "=== minting OIDC JWT (audience=pypi) ==="
|
|
71
|
+
JWT="$(curl -fsS \
|
|
72
|
+
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
|
|
73
|
+
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=pypi" |
|
|
74
|
+
"$PY" -c 'import sys,json; print(json.load(sys.stdin)["value"])')"
|
|
75
|
+
test -n "$JWT" || {
|
|
76
|
+
echo "::error::OIDC JWT request returned an empty token"
|
|
77
|
+
exit 1
|
|
78
|
+
}
|
|
79
|
+
echo "OIDC JWT obtained (length=${#JWT})"
|
|
80
|
+
|
|
81
|
+
# --- step 2: exchange the JWT for a short-lived PyPI API token ---
|
|
82
|
+
echo "=== exchanging JWT at PyPI mint-token endpoint ==="
|
|
83
|
+
MINT_RESP="$(curl -sS -X POST https://pypi.org/_/oidc/mint-token \
|
|
84
|
+
-d "{\"token\":\"${JWT}\"}")"
|
|
85
|
+
MINTED="$(printf '%s' "$MINT_RESP" |
|
|
86
|
+
"$PY" -c 'import sys,json; d=json.load(sys.stdin); print(d.get("token",""))')"
|
|
87
|
+
if [ -z "$MINTED" ]; then
|
|
88
|
+
# Surface PyPI's error body VERBATIM (the JWT is NOT echoed) so a trust
|
|
89
|
+
# misconfiguration is diagnosable — this is the decisive failure mode for
|
|
90
|
+
# the fleet. Print the raw response unconditionally (most reliable on an
|
|
91
|
+
# error path); pretty-print is best-effort on top.
|
|
92
|
+
echo "::error::PyPI mint-token returned no token."
|
|
93
|
+
echo "--- PyPI mint-token response body (raw) ---"
|
|
94
|
+
printf '%s\n' "$MINT_RESP"
|
|
95
|
+
echo "--- (pretty, best-effort) ---"
|
|
96
|
+
printf '%s' "$MINT_RESP" |
|
|
97
|
+
"$PY" -c 'import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))' \
|
|
98
|
+
2>/dev/null || true
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
echo "PyPI token minted (length=${#MINTED})"
|
|
102
|
+
|
|
103
|
+
# --- step 3: install twine into the writable target, then upload ---
|
|
104
|
+
echo "=== installing twine (--target) ==="
|
|
105
|
+
uv pip install --python "$PY" --target="$TMPDIR/site" twine ||
|
|
106
|
+
"$PY" -m pip install --target="$TMPDIR/site" twine
|
|
107
|
+
export PYTHONPATH="$TMPDIR/site${PYTHONPATH:+:$PYTHONPATH}"
|
|
108
|
+
|
|
109
|
+
echo "=== twine upload dist/* ==="
|
|
110
|
+
TWINE_USERNAME="__token__" TWINE_PASSWORD="$MINTED" \
|
|
111
|
+
"$PY" -m twine upload --non-interactive --disable-progress-bar dist/*
|
|
112
|
+
|
|
113
|
+
echo "PUBLISH-OK: scitex-storage dist/* uploaded to PyPI via manual OIDC trusted publishing"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs INSIDE the reused scitex-ci SIF (apptainer exec). $1 = python version.
|
|
3
|
+
# Adapted from scitex-ssh's .github/ci/run-in-sif.sh (the fleet's established
|
|
4
|
+
# sibling convention) — package name / import path / cov target swapped for
|
|
5
|
+
# scitex-storage; logic otherwise unchanged.
|
|
6
|
+
#
|
|
7
|
+
# WHY a layered install (not the bare PYTHONPATH=src trick scitex-dev uses):
|
|
8
|
+
# the shared ci-cpu.sif bakes scitex-dev[all,dev] DEPS, NOT scitex-storage's.
|
|
9
|
+
# So we install THIS checkout + its [all,dev] extras (WITH dependency
|
|
10
|
+
# resolution) into a writable --target dir and prepend that on PYTHONPATH. The
|
|
11
|
+
# SIF still supplies the heavy shared base (pip/uv, the python interpreters,
|
|
12
|
+
# scitex-dev's deps), so only scitex-storage's own thin dep set is fetched per
|
|
13
|
+
# run.
|
|
14
|
+
#
|
|
15
|
+
# --target (not a plain `-e .`): the SIF's /opt/venv-* are root-owned + RO and
|
|
16
|
+
# the HPC compute-node HOME is RO inside the container, so a normal site install
|
|
17
|
+
# fails Permission denied. A writable target on node-local /tmp sidesteps both.
|
|
18
|
+
#
|
|
19
|
+
# Fail-loud: a missing interpreter or a failed install is a hard error.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
V="${1:?python version arg required (3.11/3.12/3.13)}"
|
|
23
|
+
VENV="/opt/venv-$V"
|
|
24
|
+
test -x "$VENV/bin/python" || {
|
|
25
|
+
echo "::error::baked python missing in $VENV — rebuild the SIF: scitex-container apptainer build ci-cpu"
|
|
26
|
+
exit 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export LC_ALL=C.UTF-8 LANG=C.UTF-8
|
|
30
|
+
|
|
31
|
+
# Real writable scratch. The runner profile exports TMPDIR=~/.cache/tmp, a host
|
|
32
|
+
# path that does NOT resolve inside the container; tests (tmp_path) and the
|
|
33
|
+
# install target both need a working, writable tmp. Node-local /tmp is writable
|
|
34
|
+
# + ephemeral and per-version-isolated so concurrent matrix legs don't collide.
|
|
35
|
+
export TMPDIR="/tmp/ci-scitex_storage-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-$V"
|
|
36
|
+
rm -rf "$TMPDIR"
|
|
37
|
+
mkdir -p "$TMPDIR/site" "$TMPDIR/uv-cache"
|
|
38
|
+
|
|
39
|
+
# The HPC compute-node $HOME is READ-ONLY inside the container, so uv/pip cannot
|
|
40
|
+
# create their default caches under ~/.cache — point them at the writable
|
|
41
|
+
# scratch instead (else `uv pip install` dies: "failed to create directory
|
|
42
|
+
# ~/.cache/uv: File exists / read-only").
|
|
43
|
+
export UV_CACHE_DIR="$TMPDIR/uv-cache"
|
|
44
|
+
export XDG_CACHE_HOME="$TMPDIR"
|
|
45
|
+
export PIP_CACHE_DIR="$TMPDIR/pip-cache"
|
|
46
|
+
|
|
47
|
+
# A VIRTUAL_ENV leaked from the runner profile (~/.env-3.11) is a broken symlink
|
|
48
|
+
# in here; unset it so no tool (uv, pip) tries to follow it.
|
|
49
|
+
unset VIRTUAL_ENV || true
|
|
50
|
+
|
|
51
|
+
# venv bin on PATH (this matrix leg's python3 + pip); PYTHONPATH points at the
|
|
52
|
+
# writable target so imports + coverage use the freshly-installed checkout.
|
|
53
|
+
export PATH="$VENV/bin:$PATH"
|
|
54
|
+
|
|
55
|
+
echo "py=$("$VENV/bin/python" -V) target=$TMPDIR/site"
|
|
56
|
+
|
|
57
|
+
# Install scitex-storage + its [all,dev] extras WITH deps into the writable
|
|
58
|
+
# target. Fallback chain so a packaging hiccup in an optional extra doesn't
|
|
59
|
+
# strand CI: [all,dev] → [dev] → bare. uv first (fast resolver), pip as a
|
|
60
|
+
# final safety net.
|
|
61
|
+
uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e ".[all,dev]" ||
|
|
62
|
+
uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e ".[dev]" ||
|
|
63
|
+
uv pip install --python "$VENV/bin/python" --target="$TMPDIR/site" -e "." ||
|
|
64
|
+
pip install --target="$TMPDIR/site" -e ".[dev]"
|
|
65
|
+
|
|
66
|
+
export PYTHONPATH="$TMPDIR/site:$PWD/src${PYTHONPATH:+:$PYTHONPATH}"
|
|
67
|
+
|
|
68
|
+
# Parallelise with pytest-xdist if available (no-op fallback to plain -q if
|
|
69
|
+
# pytest-xdist isn't in scitex-storage's [dev] extras). Worker count: use ALL
|
|
70
|
+
# cores (each matrix leg runs on its own dedicated self-hosted node, no
|
|
71
|
+
# co-tenant). Floor 4.
|
|
72
|
+
NPROC="$(nproc 2>/dev/null || echo 4)"
|
|
73
|
+
WORKERS=$NPROC
|
|
74
|
+
[ "$WORKERS" -lt 4 ] && WORKERS=4
|
|
75
|
+
echo "xdist workers=$WORKERS (nproc=$NPROC)"
|
|
76
|
+
|
|
77
|
+
XDIST_ARGS=()
|
|
78
|
+
if python -c "import xdist" 2>/dev/null; then
|
|
79
|
+
XDIST_ARGS=(-n "$WORKERS" --dist load)
|
|
80
|
+
else
|
|
81
|
+
echo "pytest-xdist not importable — running single-process"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# nice -n 19 ionice -c 3: lowest CPU + idle I/O priority so CI yields to any
|
|
85
|
+
# higher-priority process if the node is ever shared. exec replaces the shell so
|
|
86
|
+
# signals/exit code propagate to the runner step.
|
|
87
|
+
exec nice -n 19 ionice -c 3 \
|
|
88
|
+
python -m pytest tests/ "${XDIST_ARGS[@]}" -q \
|
|
89
|
+
--cov=src/scitex_storage --cov-report=xml --cov-report=term \
|
|
90
|
+
-p no:cacheprovider
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
# Org-level reusable-workflow caller (scitex-ai/.github, fleet standard as of
|
|
4
|
+
# 2026-07-10 — see scitex-container / scitex-todo for the same pattern).
|
|
5
|
+
# Four thin `uses:` calls into scitex-ai/.github reusable workflows instead
|
|
6
|
+
# of inlining the job bodies; centrally maintained.
|
|
7
|
+
#
|
|
8
|
+
# Job ids are chosen so the resulting required-status-check context is
|
|
9
|
+
# predictable: "<job id> / <reusable job name>".
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
pull_request:
|
|
13
|
+
branches: [main, develop]
|
|
14
|
+
push:
|
|
15
|
+
branches: [main, develop]
|
|
16
|
+
schedule:
|
|
17
|
+
- cron: "0 17 * * *" # nightly 17:00 UTC (~02:00 JST), off-peak
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
pytest-matrix:
|
|
26
|
+
# Required-status-check context: "pytest-matrix / pytest-matrix-on-ubuntu-py<X.YY>"
|
|
27
|
+
uses: scitex-ai/.github/.github/workflows/pytest-matrix.yml@main
|
|
28
|
+
secrets: inherit
|
|
29
|
+
|
|
30
|
+
import-smoke:
|
|
31
|
+
# Required-status-check context: "import-smoke / import-smoke-on-ubuntu-py3-12"
|
|
32
|
+
uses: scitex-ai/.github/.github/workflows/import-smoke.yml@main
|
|
33
|
+
secrets: inherit
|
|
34
|
+
|
|
35
|
+
quality-audit:
|
|
36
|
+
# Required-status-check context: "quality-audit / audit"
|
|
37
|
+
uses: scitex-ai/.github/.github/workflows/quality-audit.yml@main
|
|
38
|
+
secrets: inherit
|
|
39
|
+
|
|
40
|
+
rtd-sphinx-build:
|
|
41
|
+
# Required-status-check context: "rtd-sphinx-build / docs-sphinx"
|
|
42
|
+
uses: scitex-ai/.github/.github/workflows/rtd-sphinx-build.yml@main
|
|
43
|
+
secrets: inherit
|
|
44
|
+
|
|
45
|
+
# Self-contained (not a shared-org `uses:` call, unlike the four jobs
|
|
46
|
+
# above) -- `scan` shells out to `fd` and `find-duplicates` to `fclones`
|
|
47
|
+
# (see src/scitex_storage/_scan.py / _duplicates.py), and `pytest-matrix`
|
|
48
|
+
# above doesn't install either binary, so the tests that exercise them
|
|
49
|
+
# for real stay skipped there. This job installs both (fast: apt for
|
|
50
|
+
# `fd-find`, the upstream release .deb for `fclones` -- no `cargo build`
|
|
51
|
+
# in CI) and reruns the suite so the real integration paths get CI
|
|
52
|
+
# coverage too.
|
|
53
|
+
fd-fclones-integration:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: "3.12"
|
|
60
|
+
- name: Install fd + fclones
|
|
61
|
+
run: |
|
|
62
|
+
sudo apt-get update -y
|
|
63
|
+
sudo apt-get install -y fd-find
|
|
64
|
+
FCLONES_VERSION=0.35.0
|
|
65
|
+
curl -fsSL -o /tmp/fclones.deb \
|
|
66
|
+
"https://github.com/pkolaczk/fclones/releases/download/v${FCLONES_VERSION}/fclones_${FCLONES_VERSION}-1_amd64.deb"
|
|
67
|
+
sudo apt-get install -y /tmp/fclones.deb
|
|
68
|
+
fdfind --version
|
|
69
|
+
fclones --version
|
|
70
|
+
- name: Install scitex-storage[dev]
|
|
71
|
+
run: pip install -e '.[dev]'
|
|
72
|
+
- name: Run tests with fd/fclones on PATH
|
|
73
|
+
run: pytest "$GITHUB_WORKSPACE/tests/" -q
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CLA Assistant
|
|
2
|
+
|
|
3
|
+
# Org-level reusable-workflow caller (scitex-ai/.github, fleet standard).
|
|
4
|
+
# Defaults (default_branch=main, owner_allowlist="bot*,ywatanabe1989",
|
|
5
|
+
# owner_login=ywatanabe1989) already match this repo — no `with:` overrides
|
|
6
|
+
# needed.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
issue_comment:
|
|
10
|
+
types: [created]
|
|
11
|
+
pull_request_target:
|
|
12
|
+
types: [opened, closed, synchronize]
|
|
13
|
+
push:
|
|
14
|
+
branches: [main, develop]
|
|
15
|
+
tags: ['v*']
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
actions: write
|
|
19
|
+
contents: write
|
|
20
|
+
pull-requests: write
|
|
21
|
+
statuses: write
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
call:
|
|
25
|
+
uses: scitex-ai/.github/.github/workflows/cla.yml@main
|
|
26
|
+
secrets: inherit
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Tag-driven release pipeline — SELF-HOSTED (Spartan CPU) via the scitex-ci SIF.
|
|
4
|
+
# Copied verbatim (structure + comments) from scitex-ssh's
|
|
5
|
+
# .github/workflows/pypi-publish-and-github-release-on-tag.yml — the fleet's
|
|
6
|
+
# established sibling convention for a scitex-* package's first (and every
|
|
7
|
+
# subsequent) PyPI release — and adapted for scitex-storage (package name,
|
|
8
|
+
# PyPI project url). See that repo for the canonical reference.
|
|
9
|
+
#
|
|
10
|
+
# The intended operator flow is:
|
|
11
|
+
#
|
|
12
|
+
# git tag vX.Y.Z && git push origin vX.Y.Z
|
|
13
|
+
#
|
|
14
|
+
# Everything else fires automatically:
|
|
15
|
+
#
|
|
16
|
+
# 1. test — matrix pytest INSIDE the SIF. Must pass or the pipeline
|
|
17
|
+
# halts; nothing is published until tests are green on the
|
|
18
|
+
# tagged commit.
|
|
19
|
+
# 2. build — wheel + sdist built INSIDE the SIF, uploaded as artifact.
|
|
20
|
+
# 3. publish — PyPI via MANUAL OIDC trusted publishing INSIDE the SIF.
|
|
21
|
+
# 4. release — GitHub Release with CHANGELOG notes + artifacts.
|
|
22
|
+
#
|
|
23
|
+
# develop→main promotion is a DELIBERATE, separate PR (operator policy);
|
|
24
|
+
# this pipeline never auto-syncs main.
|
|
25
|
+
#
|
|
26
|
+
# WHY the SIF (operator standardization 2026-06-18, fleet canary 2026-06-21):
|
|
27
|
+
# the Spartan compute runners have NO Python on the bare node, so the old
|
|
28
|
+
# `actions/setup-python@v5` step failed at init ("version 3.x not found for
|
|
29
|
+
# this OS"). The working pytest-matrix CI runs INSIDE the pre-built, reused
|
|
30
|
+
# scitex-ci SIF (`apptainer exec ci-cpu.sif`), which bakes python 3.11/3.12/
|
|
31
|
+
# 3.13 + pip + uv + fd at /opt/venv-<ver>. This release pipeline mirrors that:
|
|
32
|
+
# - test/build run `bash .github/ci/exec-in-sif.sh <inner>.sh <pyver>`.
|
|
33
|
+
# - publish CANNOT use pypa/gh-action-pypi-publish (a Docker action; the
|
|
34
|
+
# compute nodes have no Docker), so it does MANUAL OIDC trusted publishing
|
|
35
|
+
# inside the SIF (.github/ci/publish-in-sif.sh): GitHub OIDC JWT →
|
|
36
|
+
# PyPI mint-token → twine upload.
|
|
37
|
+
# - actions/checkout, upload/download-artifact are JS actions and run fine
|
|
38
|
+
# on the self-hosted runner.
|
|
39
|
+
# fail-loud: a missing SIF/shim/interpreter is a HARD error, never a
|
|
40
|
+
# bare-runner fallback.
|
|
41
|
+
#
|
|
42
|
+
# PREREQUISITE (one-time, PyPI-side, NOT part of this workflow): a Trusted
|
|
43
|
+
# Publisher must be registered on PyPI for
|
|
44
|
+
# (project=scitex-storage, owner=scitex-ai, repo=scitex-storage,
|
|
45
|
+
# workflow=release.yml, environment=pypi) BEFORE the first tag is pushed —
|
|
46
|
+
# this is an operator action on pypi.org/manage/, not something this
|
|
47
|
+
# workflow (or any CI job) can self-configure. See CHANGELOG.md /
|
|
48
|
+
# scitex-todo card scitex-storage-pypi-release for status.
|
|
49
|
+
#
|
|
50
|
+
# runs-on driven by repo Actions Variable CI_RUNS_ON; SIF + apptainer paths
|
|
51
|
+
# come from SCITEX_CI_SIF / SCITEX_CI_APPTAINER (same as pytest-matrix).
|
|
52
|
+
#
|
|
53
|
+
# Manual re-run: the workflow also accepts a `version` input via
|
|
54
|
+
# workflow_dispatch so you can re-publish without re-tagging.
|
|
55
|
+
|
|
56
|
+
on:
|
|
57
|
+
push:
|
|
58
|
+
tags: ['v*']
|
|
59
|
+
workflow_dispatch:
|
|
60
|
+
inputs:
|
|
61
|
+
version:
|
|
62
|
+
description: 'Tag to publish (e.g. v0.2.0) — must already exist on the repo'
|
|
63
|
+
required: true
|
|
64
|
+
type: string
|
|
65
|
+
|
|
66
|
+
env:
|
|
67
|
+
SCITEX_CI_APPTAINER: ${{ vars.SCITEX_CI_APPTAINER }}
|
|
68
|
+
SCITEX_CI_SIF: ${{ vars.SCITEX_CI_SIF }}
|
|
69
|
+
|
|
70
|
+
jobs:
|
|
71
|
+
test:
|
|
72
|
+
runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
|
|
73
|
+
strategy:
|
|
74
|
+
fail-fast: false
|
|
75
|
+
matrix:
|
|
76
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
77
|
+
steps:
|
|
78
|
+
- name: Resolve version
|
|
79
|
+
id: ver
|
|
80
|
+
run: |
|
|
81
|
+
if [ "${{ github.event_name }}" = "push" ]; then
|
|
82
|
+
v="${GITHUB_REF#refs/tags/}"
|
|
83
|
+
else
|
|
84
|
+
v="${{ inputs.version }}"
|
|
85
|
+
fi
|
|
86
|
+
echo "version=$v" >> "$GITHUB_OUTPUT"
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
with:
|
|
89
|
+
ref: ${{ steps.ver.outputs.version }}
|
|
90
|
+
fetch-depth: 0
|
|
91
|
+
# SIF-based self-hosted test run — mirrors pytest-matrix exactly. No
|
|
92
|
+
# setup-python (the bare Spartan node has no Python tool cache; that step
|
|
93
|
+
# failed "version not found for this OS"); no apt fd-install (fd is baked
|
|
94
|
+
# in the SIF). exec-in-sif.sh apptainer-execs the reused ci-cpu.sif and
|
|
95
|
+
# run-in-sif.sh layers THIS (tagged) checkout + [all,dev] into a writable
|
|
96
|
+
# target, then runs pytest. fail-loud on a missing SIF/shim.
|
|
97
|
+
- name: Run tests in the reused CI SIF (apptainer exec — deps layered, not on the bare runner)
|
|
98
|
+
run: bash .github/ci/exec-in-sif.sh run-in-sif.sh ${{ matrix.python-version }}
|
|
99
|
+
|
|
100
|
+
build:
|
|
101
|
+
needs: test
|
|
102
|
+
runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
|
|
103
|
+
outputs:
|
|
104
|
+
version: ${{ steps.ver.outputs.version }}
|
|
105
|
+
steps:
|
|
106
|
+
- name: Resolve version
|
|
107
|
+
id: ver
|
|
108
|
+
run: |
|
|
109
|
+
if [ "${{ github.event_name }}" = "push" ]; then
|
|
110
|
+
v="${GITHUB_REF#refs/tags/}"
|
|
111
|
+
else
|
|
112
|
+
v="${{ inputs.version }}"
|
|
113
|
+
fi
|
|
114
|
+
echo "version=$v" >> "$GITHUB_OUTPUT"
|
|
115
|
+
- uses: actions/checkout@v4
|
|
116
|
+
with:
|
|
117
|
+
ref: ${{ steps.ver.outputs.version }}
|
|
118
|
+
fetch-depth: 0
|
|
119
|
+
# Build INSIDE the SIF (no setup-python on the bare Spartan node).
|
|
120
|
+
# build-in-sif.sh installs the `build` frontend into a writable --target
|
|
121
|
+
# inside the SIF, then `python -m build` → dist/. fail-loud on empty dist.
|
|
122
|
+
- name: Build wheel + sdist in the CI SIF (apptainer exec)
|
|
123
|
+
run: bash .github/ci/exec-in-sif.sh build-in-sif.sh 3.12
|
|
124
|
+
# upload-artifact is a JS action — runs fine on the self-hosted runner.
|
|
125
|
+
- uses: actions/upload-artifact@v4
|
|
126
|
+
with:
|
|
127
|
+
name: dist
|
|
128
|
+
path: dist/
|
|
129
|
+
|
|
130
|
+
publish:
|
|
131
|
+
needs: build
|
|
132
|
+
runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
|
|
133
|
+
environment:
|
|
134
|
+
name: pypi
|
|
135
|
+
url: https://pypi.org/p/scitex-storage
|
|
136
|
+
# id-token: write is what populates ACTIONS_ID_TOKEN_REQUEST_{TOKEN,URL},
|
|
137
|
+
# which publish-in-sif.sh uses to mint the OIDC JWT. Keep it.
|
|
138
|
+
permissions:
|
|
139
|
+
id-token: write
|
|
140
|
+
steps:
|
|
141
|
+
# Need the repo checked out so .github/ci/exec-in-sif.sh +
|
|
142
|
+
# publish-in-sif.sh are on disk (the build job uploaded only dist/).
|
|
143
|
+
- uses: actions/checkout@v4
|
|
144
|
+
- uses: actions/download-artifact@v4
|
|
145
|
+
with:
|
|
146
|
+
name: dist
|
|
147
|
+
path: dist
|
|
148
|
+
# Manual OIDC trusted publishing INSIDE the SIF — pypa/gh-action-pypi-
|
|
149
|
+
# publish is a Docker action and the Spartan compute nodes have no Docker.
|
|
150
|
+
# publish-in-sif.sh: GitHub OIDC JWT (audience=pypi) → PyPI mint-token →
|
|
151
|
+
# twine upload dist/*. apptainer exec (no --cleanenv) passes the
|
|
152
|
+
# ACTIONS_ID_TOKEN_REQUEST_* env vars into the SIF. fail-loud at each step.
|
|
153
|
+
# REQUIRES a Trusted Publisher already configured on PyPI for this
|
|
154
|
+
# (project, owner, repo, workflow, environment) tuple — see the
|
|
155
|
+
# PREREQUISITE note at the top of this file.
|
|
156
|
+
- name: Publish to PyPI via manual OIDC trusted publishing in the SIF
|
|
157
|
+
run: bash .github/ci/exec-in-sif.sh publish-in-sif.sh 3.12
|
|
158
|
+
|
|
159
|
+
release:
|
|
160
|
+
needs: [build, publish]
|
|
161
|
+
runs-on: ${{ fromJSON(vars.CI_RUNS_ON || '["self-hosted","Linux","X64","scitex-ci"]') }}
|
|
162
|
+
permissions:
|
|
163
|
+
contents: write
|
|
164
|
+
steps:
|
|
165
|
+
- uses: actions/checkout@v4
|
|
166
|
+
with:
|
|
167
|
+
ref: ${{ needs.build.outputs.version }}
|
|
168
|
+
- uses: actions/download-artifact@v4
|
|
169
|
+
with:
|
|
170
|
+
name: dist
|
|
171
|
+
path: dist
|
|
172
|
+
- name: Extract release notes from CHANGELOG.md
|
|
173
|
+
run: |
|
|
174
|
+
VERSION="${{ needs.build.outputs.version }}"
|
|
175
|
+
VERSION="${VERSION#v}"
|
|
176
|
+
awk -v v="$VERSION" '
|
|
177
|
+
$0 ~ "^## \\[" v "\\]" {p=1; next}
|
|
178
|
+
p && /^## \[/ {exit}
|
|
179
|
+
p
|
|
180
|
+
' CHANGELOG.md > release-notes.md || true
|
|
181
|
+
if [ ! -s release-notes.md ]; then
|
|
182
|
+
echo "Release v${VERSION}. See CHANGELOG.md for details." > release-notes.md
|
|
183
|
+
fi
|
|
184
|
+
- name: Create GitHub Release
|
|
185
|
+
env:
|
|
186
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
187
|
+
run: |
|
|
188
|
+
gh release create "${{ needs.build.outputs.version }}" \
|
|
189
|
+
--title "${{ needs.build.outputs.version }}" \
|
|
190
|
+
--notes-file release-notes.md \
|
|
191
|
+
dist/*
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.eggs/
|
|
10
|
+
*.whl
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
.env
|
|
15
|
+
.tox/
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
*~
|
|
22
|
+
.DS_Store
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
|
|
26
|
+
# direnv + git worktrees live in-tree but are never committed.
|
|
27
|
+
.envrc
|
|
28
|
+
.worktrees/
|
|
29
|
+
|
|
30
|
+
# scitex-dev local state: ignore everything under .scitex/ except the
|
|
31
|
+
# tracked dev/config.yaml and the CLI-audit noun/verb dict (both are audit
|
|
32
|
+
# inputs CI must see on a fresh checkout).
|
|
33
|
+
.scitex/*
|
|
34
|
+
!.scitex/dev/
|
|
35
|
+
.scitex/dev/*
|
|
36
|
+
!.scitex/dev/config.yaml
|
|
37
|
+
!.scitex/dev/cli-audit-dict.yaml
|
|
38
|
+
|
|
39
|
+
# Sphinx local build output (the in-wheel src/<pkg>/_sphinx_html bundle IS tracked)
|
|
40
|
+
docs/sphinx/_build/
|
|
41
|
+
|
|
42
|
+
# Operator's private brainstorm notes — never committed (see repo instructions).
|
|
43
|
+
GITIGNORED/
|