Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
111b891
chore: normalize line endings to LF
rosjat Jul 30, 2026
a788288
chore: drop .travis.yml and move mypy config to pyproject
rosjat Jul 30, 2026
bc54cdf
chore: bump pre-commit hooks and repoint isort
rosjat Jul 30, 2026
87abd2b
test: add import, readdiscinformation and table tests
rosjat Jul 30, 2026
479cc98
build: migrate packaging to PEP 621 pyproject
rosjat Jul 30, 2026
383bf8c
style: reformat for the declared 3.11 target
rosjat Jul 30, 2026
e98da50
ci: add test matrix and modernize workflows
rosjat Jul 30, 2026
f0ffae0
ci: fix TestPyPI smoke test version and bump artifact actions
rosjat Jul 30, 2026
79a7aa9
ci: replace pre-commit/action with a direct call
rosjat Jul 30, 2026
51b3153
ci: correct publish auth per index and gate releases on master
rosjat Jul 30, 2026
fa36918
fix: correct the CDB group range check in init_cdb
rosjat Jul 30, 2026
3e55691
fix: spell two sbc opcode names per the standard
rosjat Jul 31, 2026
94381d9
refactor: drop the implicit object base class
rosjat Jul 31, 2026
0fc13e9
ci: publish the development container to ghcr
rosjat Jul 31, 2026
5cf252f
ci: move the docker actions off deprecated Node 20
rosjat Jul 31, 2026
ca45c00
build: carry every supported runtime in one dev image
rosjat Jul 31, 2026
9b46cef
build: build the transports from source, not from PyPI
rosjat Jul 31, 2026
00b3a20
style: drop comments that restate the code
rosjat Aug 1, 2026
94ad0de
ci: push only :latest and prune old dev images
rosjat Aug 1, 2026
328d8a2
ci: build a version-tagged image on release tags
rosjat Aug 1, 2026
9ca32f2
ci: run mypy against every supported version
rosjat Aug 1, 2026
915bfb1
docs: rewrite DEVELOPMENT.md for the container workflow
rosjat Aug 1, 2026
9097686
refactor!: replace the Enum metaclass with typed tables
rosjat Aug 1, 2026
815b156
fix!: give each command its own CDB field map
rosjat Aug 1, 2026
57f4482
fix: drop the stray positional argument in ModeSelect10
rosjat Aug 1, 2026
07ee152
fix: correct the REPORT LUNS parameter data header size
rosjat Aug 1, 2026
d3ca2c0
refactor: move TransportID handling into a shared module
rosjat Aug 1, 2026
1c14168
fix: repair the REPORT PRIORITY datain path
rosjat Aug 1, 2026
298acf8
fix: marshall REPORT TARGET PORT GROUPS port descriptors
rosjat Aug 1, 2026
64a28e9
fix: two defects in the INQUIRY VPD pages
rosjat Aug 1, 2026
2178a6c
test: check READ CD main channel selection against MMC table 354
rosjat Aug 1, 2026
6dca400
fix: name the offending key in EXTENDED COPY validation
rosjat Aug 1, 2026
cba757f
refactor: annotate converter
rosjat Aug 2, 2026
96b60c6
refactor: annotate scsi_sense
rosjat Aug 2, 2026
638fa71
refactor: annotate scsi_exception
rosjat Aug 2, 2026
7f4c7da
refactor: annotate scsi_command
rosjat Aug 2, 2026
c7d2f5b
fix: allow WRITE SAME (16) with NDOB over iSCSI
rosjat Aug 2, 2026
f28d962
refactor: annotate iscsi_device
rosjat Aug 2, 2026
7ca5918
refactor: annotate scsi_device
rosjat Aug 2, 2026
82bc5f0
refactor: annotate the simple command modules
rosjat Aug 2, 2026
35d3b0b
refactor: annotate the mid-tier command modules
rosjat Aug 2, 2026
8628794
fix: correct the stream-to-block segment descriptor lookup
rosjat Aug 2, 2026
c83a58f
refactor: annotate the heavy modules and give the type aliases a home
rosjat Aug 2, 2026
c904923
fix: declare every injected exception on all three host classes
rosjat Aug 2, 2026
f0b62f3
refactor: type the device contract as a Protocol
rosjat Aug 2, 2026
bffb7e0
fix: build each host class once in the exception metaclass
rosjat Aug 2, 2026
a9dddb1
refactor: make the command result type generic
rosjat Aug 2, 2026
ccc2e50
refactor: annotate the SCSI facade
rosjat Aug 2, 2026
e94e73f
refactor: annotate the test suite and drop the mypy overrides
rosjat Aug 2, 2026
e9d5cfc
chore: extend the copyright year to 2026
rosjat Aug 2, 2026
51cb182
docs: rewrite the README for 3.0 and retire the 2.x one
rosjat Aug 2, 2026
f9d8b67
feat: add pyscsi-report to the dev image
rosjat Aug 2, 2026
2b52676
ci: build the image on master and release tags only
rosjat Aug 2, 2026
554ad19
ci: drop the reference to a file that is not in the repository
rosjat Aug 2, 2026
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
24 changes: 24 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2026 The python-scsi Authors
#
# SPDX-License-Identifier: MIT

# Normalize every text file to LF in the working tree as well as in the object
# store. Without this, a Windows checkout with core.autocrlf=true produces CRLF
# working-tree files while the blobs stay LF; a Linux container bind-mounting
# that tree would then commit CRLF blobs back into history.
* text=auto eol=lf

# Batch files genuinely misparse labels and goto targets with LF endings.
# PowerShell handles LF fine, so .ps1 deliberately stays on the LF default.
*.bat text eol=crlf
*.cmd text eol=crlf

# Never touch these.
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.pdf binary
*.gz binary
*.zip binary
*.whl binary
49 changes: 49 additions & 0 deletions .github/workflows/container-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# SPDX-FileCopyrightText: 2026 The python-scsi Authors
#
# SPDX-License-Identifier: MIT

name: container-release

# Separate from container.yml because path filters are not applied to tag
# pushes, so a `tags` filter beside its `paths` filter would never build.
on:
push:
tags: ['v*']

permissions:
contents: read

jobs:
# A tag alone does not authorise a release image: without this, tagging any
# branch would publish one. Its own job because container.yml is called at
# job level, so no step can run ahead of it.
#
# master is hardcoded rather than read from the default branch, so a fork
# whose default is a working branch cannot widen it. Mirrors pypi.yml.
guard:
name: Refuse a tag that is not merged into master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- env:
DEFAULT_BRANCH: master
run: |
set -euo pipefail
git fetch --no-tags origin \
"+refs/heads/$DEFAULT_BRANCH:refs/remotes/origin/$DEFAULT_BRANCH"
if git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH"; then
echo "Tag $GITHUB_REF_NAME is on $DEFAULT_BRANCH; proceeding."
else
echo "::error::Tag $GITHUB_REF_NAME points at a commit that is not"\
"merged into $DEFAULT_BRANCH. Refusing to build a release image."
exit 1
fi

container:
needs: guard
uses: ./.github/workflows/container.yml
permissions:
contents: read
packages: write
143 changes: 143 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# SPDX-FileCopyrightText: 2026 The python-scsi Authors
#
# SPDX-License-Identifier: MIT

name: container

# master only. A new image is needed when the Containerfile changes, and a
# change is only known to be good once it is merged -- feature branches build
# the image locally instead with `podman build -f containers/Containerfile`.
#
# Tag builds come through container-release.yml, which calls this workflow:
# path filters are not applied to tag pushes, so a `tags` filter here would
# never match.
on:
push:
branches: [master]
paths:
- 'containers/Containerfile'
- '.github/workflows/container.yml'
workflow_dispatch:
workflow_call:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
container:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7

# ghcr rejects an uppercase path.
- name: Resolve the image name
id: image
run: |
owner=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
ref="ghcr.io/${owner}/python-scsi-dev"
echo "ref=${ref}" >> "$GITHUB_OUTPUT"
{
echo 'tags<<TAGS'
echo "${ref}:latest"
case "$GITHUB_REF" in
refs/tags/*) echo "${ref}:${GITHUB_REF#refs/tags/}" ;;
esac
echo TAGS
} >> "$GITHUB_OUTPUT"

# `master` would cache on that literal string and keep whatever was built
# first; a SHA changes when upstream does, so the layer rebuilds.
- name: Resolve the transport revisions
id: transports
run: |
set -eu
for repo in cython-sgio cython-iscsi; do
sha=$(git ls-remote "https://github.com/python-scsi/${repo}.git" \
refs/heads/master | cut -f1)
[ -n "$sha" ] || { echo "could not resolve ${repo} master" >&2; exit 1; }
echo "${repo}: ${sha}"
echo "${repo//-/_}=${sha}" >> "$GITHUB_OUTPUT"
done

- uses: docker/setup-buildx-action@v4

- name: Log in to ghcr.io
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v7
with:
context: containers
file: containers/Containerfile
build-args: |
SGIO_REF=${{ steps.transports.outputs.cython_sgio }}
ISCSI_REF=${{ steps.transports.outputs.cython_iscsi }}
platforms: linux/amd64
push: true
load: true
tags: ${{ steps.image.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke test the image
env:
IMAGE: ${{ steps.image.outputs.ref }}:latest
run: |
set -eu
echo "--- every runtime, with the transport builds it got ---"
docker run --rm "$IMAGE" bash -c '
for v in $PYSCSI_VERSIONS; do
"/opt/venv/$v/bin/python" -c "
import sgio, iscsi
from importlib.metadata import version
print(\" python $v cython-sgio %-24s cython-iscsi %s\"
% (version(\"cython-sgio\"), version(\"cython-iscsi\")))
"
done'

echo "--- harness commands are on PATH ---"
docker run --rm "$IMAGE" bash -c \
'command -v pyscsi-matrix pyscsi-report pyscsi-tgt-setup \
pyscsi-verify-tools pyscsi-verify-iscsi'

echo "--- pyscsi-report parses its flags and rejects unknown ones ---"
docker run --rm "$IMAGE" pyscsi-report --help
if docker run --rm "$IMAGE" pyscsi-report --not-a-flag; then
echo "::error::pyscsi-report accepted an unknown flag"
exit 1
fi

echo "--- emulated targets come up with no repository mounted ---"
docker run --rm "$IMAGE" pyscsi-tgt-setup

echo "--- the repository's own tests pass in the image ---"
docker run --rm -v "$PWD:/src" -w /src "$IMAGE" pytest -q

# Two passes: delete-only-untagged-versions makes the action disregard
# ignore-versions.
- name: Delete untagged images
uses: actions/delete-package-versions@v5
with:
package-name: python-scsi-dev
package-type: container
delete-only-untagged-versions: true
min-versions-to-keep: 0

- name: Delete superseded images
uses: actions/delete-package-versions@v5
with:
package-name: python-scsi-dev
package-type: container
min-versions-to-keep: 3
ignore-versions: '^(latest|v?[0-9]+\.[0-9]+\.[0-9]+)$'
33 changes: 29 additions & 4 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2019 Anthony Sottile
# SPDX-FileCopyrightText: 2026 The python-scsi Authors
#
# SPDX-License-Identifier: MIT

Expand All @@ -7,12 +8,36 @@ name: pre-commit
on:
pull_request:
push:
branches: [master]
# Every branch, not just master: development happens on feature branches
# and forks.
branches: ['**']

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/[email protected]
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
# Pin to the project floor. Left unset, the job silently uses whatever
# Python is on PATH, so hook behaviour could drift with the runner
# image.
python-version: '3.11'
# pre-commit is invoked directly rather than through pre-commit/action.
# That action's last release is v3.0.1 (Feb 2024) and it pulls in
# actions/cache@v4, which targets the deprecated Node 20; running
# pre-commit ourselves removes the indirection and lets the cache action
# be kept current.
- uses: actions/cache@v6
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-py3.11-${{ hashFiles('.pre-commit-config.yaml') }}
- run: python -m pip install pre-commit
- run: pre-commit run --all-files --show-diff-on-failure
Loading