batchvault 0.1.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.
- batchvault-0.1.0/.dockerignore +20 -0
- batchvault-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +38 -0
- batchvault-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- batchvault-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +14 -0
- batchvault-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- batchvault-0.1.0/.github/dependabot.yml +33 -0
- batchvault-0.1.0/.github/workflows/docker.yml +77 -0
- batchvault-0.1.0/.github/workflows/e2e.yml +338 -0
- batchvault-0.1.0/.github/workflows/lint.yml +27 -0
- batchvault-0.1.0/.github/workflows/release.yml +79 -0
- batchvault-0.1.0/.github/workflows/tests.yml +45 -0
- batchvault-0.1.0/.gitignore +223 -0
- batchvault-0.1.0/.pre-commit-config.yaml +17 -0
- batchvault-0.1.0/.python-version +1 -0
- batchvault-0.1.0/CHANGELOG.md +26 -0
- batchvault-0.1.0/CODE_OF_CONDUCT.md +15 -0
- batchvault-0.1.0/CONTRIBUTING.md +34 -0
- batchvault-0.1.0/Dockerfile +35 -0
- batchvault-0.1.0/LICENSE +21 -0
- batchvault-0.1.0/PKG-INFO +306 -0
- batchvault-0.1.0/README.md +273 -0
- batchvault-0.1.0/SECURITY.md +51 -0
- batchvault-0.1.0/action.yml +255 -0
- batchvault-0.1.0/docs/architecture.md +164 -0
- batchvault-0.1.0/docs/cli.md +361 -0
- batchvault-0.1.0/docs/compression.md +199 -0
- batchvault-0.1.0/docs/configuration.md +75 -0
- batchvault-0.1.0/docs/development.md +30 -0
- batchvault-0.1.0/docs/docker.md +84 -0
- batchvault-0.1.0/docs/github-action.md +138 -0
- batchvault-0.1.0/docs/python.md +173 -0
- batchvault-0.1.0/docs/testing.md +99 -0
- batchvault-0.1.0/pyproject.toml +53 -0
- batchvault-0.1.0/src/batchvault/__init__.py +61 -0
- batchvault-0.1.0/src/batchvault/__main__.py +3 -0
- batchvault-0.1.0/src/batchvault/api.py +884 -0
- batchvault-0.1.0/src/batchvault/blocks.py +176 -0
- batchvault-0.1.0/src/batchvault/cli.py +806 -0
- batchvault-0.1.0/src/batchvault/config.py +169 -0
- batchvault-0.1.0/src/batchvault/crypto.py +193 -0
- batchvault-0.1.0/src/batchvault/doctor.py +67 -0
- batchvault-0.1.0/src/batchvault/engines/__init__.py +0 -0
- batchvault-0.1.0/src/batchvault/engines/base.py +57 -0
- batchvault-0.1.0/src/batchvault/engines/native.py +237 -0
- batchvault-0.1.0/src/batchvault/engines/registry.py +61 -0
- batchvault-0.1.0/src/batchvault/engines/sevenzip.py +303 -0
- batchvault-0.1.0/src/batchvault/errors.py +38 -0
- batchvault-0.1.0/src/batchvault/eta.py +39 -0
- batchvault-0.1.0/src/batchvault/filters.py +51 -0
- batchvault-0.1.0/src/batchvault/inputs.py +226 -0
- batchvault-0.1.0/src/batchvault/levels.py +169 -0
- batchvault-0.1.0/src/batchvault/locate.py +140 -0
- batchvault-0.1.0/src/batchvault/logs.py +86 -0
- batchvault-0.1.0/src/batchvault/manifest.py +210 -0
- batchvault-0.1.0/src/batchvault/models.py +164 -0
- batchvault-0.1.0/src/batchvault/names.py +36 -0
- batchvault-0.1.0/src/batchvault/notify.py +73 -0
- batchvault-0.1.0/src/batchvault/options.py +71 -0
- batchvault-0.1.0/src/batchvault/py.typed +0 -0
- batchvault-0.1.0/src/batchvault/security.py +40 -0
- batchvault-0.1.0/src/batchvault/signing.py +152 -0
- batchvault-0.1.0/src/batchvault/ui.py +298 -0
- batchvault-0.1.0/src/batchvault/volumes.py +201 -0
- batchvault-0.1.0/tests/conftest.py +19 -0
- batchvault-0.1.0/tests/e2e/conftest.py +38 -0
- batchvault-0.1.0/tests/e2e/scripts/compare.py +48 -0
- batchvault-0.1.0/tests/e2e/scripts/make_data.py +37 -0
- batchvault-0.1.0/tests/e2e/test_cli_e2e.py +335 -0
- batchvault-0.1.0/tests/e2e/test_docker_e2e.py +199 -0
- batchvault-0.1.0/tests/e2e/test_package_e2e.py +240 -0
- batchvault-0.1.0/tests/integration/test_sevenzip_api.py +154 -0
- batchvault-0.1.0/tests/integration/test_sevenzip_roundtrip.py +70 -0
- batchvault-0.1.0/tests/test_api.py +249 -0
- batchvault-0.1.0/tests/test_cli.py +170 -0
- batchvault-0.1.0/tests/test_cli_features.py +177 -0
- batchvault-0.1.0/tests/test_config.py +163 -0
- batchvault-0.1.0/tests/test_crypto.py +145 -0
- batchvault-0.1.0/tests/test_eta.py +30 -0
- batchvault-0.1.0/tests/test_features.py +265 -0
- batchvault-0.1.0/tests/test_filters.py +31 -0
- batchvault-0.1.0/tests/test_folders.py +318 -0
- batchvault-0.1.0/tests/test_hardening.py +320 -0
- batchvault-0.1.0/tests/test_inputs.py +135 -0
- batchvault-0.1.0/tests/test_levels.py +266 -0
- batchvault-0.1.0/tests/test_logs.py +30 -0
- batchvault-0.1.0/tests/test_manifest.py +87 -0
- batchvault-0.1.0/tests/test_native.py +209 -0
- batchvault-0.1.0/tests/test_notify.py +71 -0
- batchvault-0.1.0/tests/test_options.py +37 -0
- batchvault-0.1.0/tests/test_sevenzip.py +101 -0
- batchvault-0.1.0/tests/test_signing.py +148 -0
- batchvault-0.1.0/tests/test_summary.py +198 -0
- batchvault-0.1.0/tests/test_volume_io.py +46 -0
- batchvault-0.1.0/tests/test_volumes.py +56 -0
- batchvault-0.1.0/uv.lock +523 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something does not work as expected
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened?
|
|
9
|
+
description: What did you run, what did you expect, and what happened instead?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: command
|
|
14
|
+
attributes:
|
|
15
|
+
label: Command or code
|
|
16
|
+
render: shell
|
|
17
|
+
- type: input
|
|
18
|
+
id: version
|
|
19
|
+
attributes:
|
|
20
|
+
label: BatchVault version
|
|
21
|
+
description: Output of `batchvault --version`
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: dropdown
|
|
25
|
+
id: engine
|
|
26
|
+
attributes:
|
|
27
|
+
label: Engine
|
|
28
|
+
options: [auto, native, 7zip]
|
|
29
|
+
- type: input
|
|
30
|
+
id: platform
|
|
31
|
+
attributes:
|
|
32
|
+
label: OS and Python version
|
|
33
|
+
- type: textarea
|
|
34
|
+
id: log
|
|
35
|
+
attributes:
|
|
36
|
+
label: Log or error output
|
|
37
|
+
description: Run `batchvault doctor` and attach the relevant lines of the log file. Remove anything private.
|
|
38
|
+
render: text
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest an improvement
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What are you trying to do?
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: idea
|
|
13
|
+
attributes:
|
|
14
|
+
label: What would you like BatchVault to do?
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## What changed
|
|
2
|
+
|
|
3
|
+
<!-- What does this do, and why? -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] Tests added or updated
|
|
8
|
+
- [ ] `uv run ruff check .` and `uv run pytest -m "not integration and not e2e"` pass
|
|
9
|
+
- [ ] `CHANGELOG.md` has a short `[Unreleased]` entry
|
|
10
|
+
- [ ] README or `docs/` updated if behaviour changed
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
# Python dependencies and the uv.lock file
|
|
5
|
+
- package-ecosystem: "uv"
|
|
6
|
+
directory: "/"
|
|
7
|
+
schedule:
|
|
8
|
+
interval: "weekly"
|
|
9
|
+
commit-message:
|
|
10
|
+
prefix: "chore(deps)"
|
|
11
|
+
groups:
|
|
12
|
+
python-dependencies:
|
|
13
|
+
patterns: ["*"]
|
|
14
|
+
update-types: ["minor", "patch"]
|
|
15
|
+
|
|
16
|
+
# Actions used in .github/workflows
|
|
17
|
+
- package-ecosystem: "github-actions"
|
|
18
|
+
directory: "/"
|
|
19
|
+
schedule:
|
|
20
|
+
interval: "weekly"
|
|
21
|
+
commit-message:
|
|
22
|
+
prefix: "chore(ci)"
|
|
23
|
+
groups:
|
|
24
|
+
github-actions:
|
|
25
|
+
patterns: ["*"]
|
|
26
|
+
|
|
27
|
+
# Hook versions in .pre-commit-config.yaml
|
|
28
|
+
- package-ecosystem: "pre-commit"
|
|
29
|
+
directory: "/"
|
|
30
|
+
schedule:
|
|
31
|
+
interval: "weekly"
|
|
32
|
+
commit-message:
|
|
33
|
+
prefix: "chore(deps)"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: Docker
|
|
2
|
+
|
|
3
|
+
# Builds the image on every pull request and publishes both variants to GHCR
|
|
4
|
+
# when a version tag such as v0.2.0 is pushed.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*.*.*"]
|
|
8
|
+
pull_request:
|
|
9
|
+
paths:
|
|
10
|
+
- Dockerfile
|
|
11
|
+
- .dockerignore
|
|
12
|
+
- pyproject.toml
|
|
13
|
+
- src/**
|
|
14
|
+
- .github/workflows/docker.yml
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: docker-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
image:
|
|
25
|
+
name: Image (${{ matrix.variant }})
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
packages: write
|
|
30
|
+
strategy:
|
|
31
|
+
fail-fast: false
|
|
32
|
+
matrix:
|
|
33
|
+
include:
|
|
34
|
+
- { variant: native, with_7zip: "false", suffix: "" }
|
|
35
|
+
- { variant: 7zip, with_7zip: "true", suffix: "-7zip" }
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v7
|
|
39
|
+
|
|
40
|
+
- uses: docker/setup-qemu-action@v3
|
|
41
|
+
|
|
42
|
+
- uses: docker/setup-buildx-action@v3
|
|
43
|
+
|
|
44
|
+
- name: Log in to GHCR
|
|
45
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
46
|
+
uses: docker/login-action@v3
|
|
47
|
+
with:
|
|
48
|
+
registry: ghcr.io
|
|
49
|
+
username: ${{ github.actor }}
|
|
50
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
|
|
52
|
+
- name: Tags
|
|
53
|
+
id: meta
|
|
54
|
+
uses: docker/metadata-action@v5
|
|
55
|
+
with:
|
|
56
|
+
images: ghcr.io/${{ github.repository_owner }}/batchvault
|
|
57
|
+
flavor: |
|
|
58
|
+
latest=false
|
|
59
|
+
suffix=${{ matrix.suffix }}
|
|
60
|
+
tags: |
|
|
61
|
+
type=semver,pattern={{version}}
|
|
62
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
63
|
+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
|
64
|
+
|
|
65
|
+
- name: Build${{ startsWith(github.ref, 'refs/tags/') && ' and push' || '' }}
|
|
66
|
+
uses: docker/build-push-action@v6
|
|
67
|
+
with:
|
|
68
|
+
context: .
|
|
69
|
+
platforms: linux/amd64,linux/arm64
|
|
70
|
+
build-args: WITH_7ZIP=${{ matrix.with_7zip }}
|
|
71
|
+
push: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
72
|
+
provenance: mode=max
|
|
73
|
+
sbom: true
|
|
74
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
75
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
76
|
+
cache-from: type=gha,scope=${{ matrix.variant }}
|
|
77
|
+
cache-to: type=gha,mode=max,scope=${{ matrix.variant }}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
name: End-to-end
|
|
2
|
+
|
|
3
|
+
# Runs the real program the way users do: the command line and the built package
|
|
4
|
+
# (tests/e2e), the GitHub Action (this repository's action.yml, used as "./"),
|
|
5
|
+
# and the Docker images. Also run before every release (see release.yml).
|
|
6
|
+
on:
|
|
7
|
+
workflow_call:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
pull_request:
|
|
12
|
+
paths:
|
|
13
|
+
- src/**
|
|
14
|
+
- tests/e2e/**
|
|
15
|
+
- action.yml
|
|
16
|
+
- Dockerfile
|
|
17
|
+
- .dockerignore
|
|
18
|
+
- pyproject.toml
|
|
19
|
+
- uv.lock
|
|
20
|
+
- .github/workflows/e2e.yml
|
|
21
|
+
schedule:
|
|
22
|
+
- cron: "0 4 * * 1" # Mondays; catches changes in runners, pip and Docker
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: e2e-${{ github.ref }}
|
|
26
|
+
cancel-in-progress: true
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
cli-and-package:
|
|
33
|
+
name: CLI and package / ${{ matrix.os }} / Python ${{ matrix.python }}
|
|
34
|
+
runs-on: ${{ matrix.os }}
|
|
35
|
+
# Same matrix as tests.yml. Python 3.15 may still be a pre-release; don't
|
|
36
|
+
# let it block the build.
|
|
37
|
+
continue-on-error: ${{ matrix.python == '3.15' }}
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
include:
|
|
42
|
+
- { os: ubuntu-latest, python: "3.11" }
|
|
43
|
+
- { os: ubuntu-latest, python: "3.12" }
|
|
44
|
+
- { os: ubuntu-latest, python: "3.13" }
|
|
45
|
+
- { os: ubuntu-latest, python: "3.14" }
|
|
46
|
+
- { os: ubuntu-latest, python: "3.15" }
|
|
47
|
+
- { os: windows-latest, python: "3.11" }
|
|
48
|
+
- { os: macos-latest, python: "3.11" }
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v7
|
|
51
|
+
|
|
52
|
+
- uses: astral-sh/setup-uv@v7
|
|
53
|
+
|
|
54
|
+
- name: Install
|
|
55
|
+
run: uv sync --python ${{ matrix.python }}
|
|
56
|
+
|
|
57
|
+
# No -n: the package tests build a wheel and virtual environments once.
|
|
58
|
+
- name: CLI and package tests
|
|
59
|
+
run: uv run pytest -m "e2e and not docker" -v
|
|
60
|
+
|
|
61
|
+
docker:
|
|
62
|
+
name: Docker images
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v7
|
|
66
|
+
|
|
67
|
+
- uses: astral-sh/setup-uv@v7
|
|
68
|
+
|
|
69
|
+
- name: Install
|
|
70
|
+
run: uv sync --python 3.11
|
|
71
|
+
|
|
72
|
+
- name: Docker tests (builds both images)
|
|
73
|
+
run: uv run pytest -m docker -v
|
|
74
|
+
|
|
75
|
+
action:
|
|
76
|
+
name: GitHub Action / ${{ matrix.os }}
|
|
77
|
+
runs-on: ${{ matrix.os }}
|
|
78
|
+
strategy:
|
|
79
|
+
fail-fast: false
|
|
80
|
+
matrix:
|
|
81
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
82
|
+
defaults:
|
|
83
|
+
run:
|
|
84
|
+
shell: bash
|
|
85
|
+
env:
|
|
86
|
+
PYTHONUTF8: "1"
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v7
|
|
89
|
+
|
|
90
|
+
- uses: actions/setup-python@v5
|
|
91
|
+
with:
|
|
92
|
+
python-version: "3.12"
|
|
93
|
+
|
|
94
|
+
# Only used by the steps below that create keys or edit files; the action
|
|
95
|
+
# installs its own copy.
|
|
96
|
+
- name: Install BatchVault for helper steps
|
|
97
|
+
run: python -m pip install --quiet ".[crypto]"
|
|
98
|
+
|
|
99
|
+
- name: Create test data
|
|
100
|
+
run: python tests/e2e/scripts/make_data.py data
|
|
101
|
+
|
|
102
|
+
# --- pack, verify, unpack -------------------------------------------
|
|
103
|
+
- name: Pack
|
|
104
|
+
id: pack
|
|
105
|
+
uses: ./
|
|
106
|
+
with:
|
|
107
|
+
files: |
|
|
108
|
+
data/report.bin
|
|
109
|
+
data/notes.txt
|
|
110
|
+
data/ünï cödé.txt
|
|
111
|
+
data/empty.dat
|
|
112
|
+
data/sub dir/nested file.csv
|
|
113
|
+
output: archives
|
|
114
|
+
engine: native
|
|
115
|
+
volume-size: 256k
|
|
116
|
+
verify: "true"
|
|
117
|
+
|
|
118
|
+
- name: Pack reported success and wrote a manifest
|
|
119
|
+
env:
|
|
120
|
+
STATUS: ${{ steps.pack.outputs.status }}
|
|
121
|
+
OUTPUT: ${{ steps.pack.outputs.output }}
|
|
122
|
+
RESULT: ${{ steps.pack.outputs.result-json }}
|
|
123
|
+
run: |
|
|
124
|
+
test "$STATUS" = success
|
|
125
|
+
test "$OUTPUT" = archives
|
|
126
|
+
test -f archives/manifest.json
|
|
127
|
+
test -s "$RESULT"
|
|
128
|
+
python -c "import json,sys; d=json.load(open(sys.argv[1], encoding='utf-8')); assert len(d['entries'])==5" "$RESULT"
|
|
129
|
+
|
|
130
|
+
- name: Verify against the originals
|
|
131
|
+
id: verify
|
|
132
|
+
uses: ./
|
|
133
|
+
with:
|
|
134
|
+
command: verify
|
|
135
|
+
source: archives
|
|
136
|
+
against: data
|
|
137
|
+
|
|
138
|
+
- name: Unpack
|
|
139
|
+
id: unpack
|
|
140
|
+
uses: ./
|
|
141
|
+
with:
|
|
142
|
+
command: unpack
|
|
143
|
+
source: archives
|
|
144
|
+
output: restored
|
|
145
|
+
|
|
146
|
+
- name: Restored files are identical
|
|
147
|
+
env:
|
|
148
|
+
VERIFY: ${{ steps.verify.outputs.status }}
|
|
149
|
+
UNPACK: ${{ steps.unpack.outputs.status }}
|
|
150
|
+
run: |
|
|
151
|
+
test "$VERIFY" = success
|
|
152
|
+
test "$UNPACK" = success
|
|
153
|
+
python tests/e2e/scripts/compare.py data restored
|
|
154
|
+
|
|
155
|
+
# --- incremental mode -----------------------------------------------
|
|
156
|
+
- name: Pack again with update (nothing changed)
|
|
157
|
+
id: update
|
|
158
|
+
uses: ./
|
|
159
|
+
with:
|
|
160
|
+
files: |
|
|
161
|
+
data/report.bin
|
|
162
|
+
data/notes.txt
|
|
163
|
+
output: archives
|
|
164
|
+
engine: native
|
|
165
|
+
volume-size: 256k
|
|
166
|
+
update: "true"
|
|
167
|
+
fail-on-error: "false"
|
|
168
|
+
|
|
169
|
+
- name: Nothing was re-archived
|
|
170
|
+
env:
|
|
171
|
+
STATUS: ${{ steps.update.outputs.status }}
|
|
172
|
+
run: test "$STATUS" = skipped
|
|
173
|
+
|
|
174
|
+
# --- artifact upload ------------------------------------------------
|
|
175
|
+
- name: Pack and upload as an artifact
|
|
176
|
+
uses: ./
|
|
177
|
+
with:
|
|
178
|
+
files: data/notes.txt
|
|
179
|
+
output: uploaded
|
|
180
|
+
engine: native
|
|
181
|
+
upload-artifact: "true"
|
|
182
|
+
artifact-name: e2e-archives-${{ matrix.os }}
|
|
183
|
+
|
|
184
|
+
- name: Download the artifact
|
|
185
|
+
uses: actions/download-artifact@v4
|
|
186
|
+
with:
|
|
187
|
+
name: e2e-archives-${{ matrix.os }}
|
|
188
|
+
path: downloaded
|
|
189
|
+
|
|
190
|
+
- name: Artifact holds the archive
|
|
191
|
+
run: |
|
|
192
|
+
test -f downloaded/manifest.json
|
|
193
|
+
ls downloaded/notes.txt
|
|
194
|
+
|
|
195
|
+
# --- encryption -----------------------------------------------------
|
|
196
|
+
- name: Pack encrypted
|
|
197
|
+
uses: ./
|
|
198
|
+
with:
|
|
199
|
+
files: data/notes.txt
|
|
200
|
+
output: vault
|
|
201
|
+
engine: native
|
|
202
|
+
encrypt: "true"
|
|
203
|
+
password: e2e-throwaway-password
|
|
204
|
+
|
|
205
|
+
- name: Unpack with the wrong password
|
|
206
|
+
id: wrong
|
|
207
|
+
uses: ./
|
|
208
|
+
with:
|
|
209
|
+
command: unpack
|
|
210
|
+
source: vault
|
|
211
|
+
output: wrong-restore
|
|
212
|
+
password: not-the-password
|
|
213
|
+
fail-on-error: "false"
|
|
214
|
+
|
|
215
|
+
- name: Unpack with the right password
|
|
216
|
+
id: right
|
|
217
|
+
uses: ./
|
|
218
|
+
with:
|
|
219
|
+
command: unpack
|
|
220
|
+
source: vault
|
|
221
|
+
output: right-restore
|
|
222
|
+
password: e2e-throwaway-password
|
|
223
|
+
|
|
224
|
+
- name: Only the right password restores
|
|
225
|
+
env:
|
|
226
|
+
WRONG: ${{ steps.wrong.outputs.status }}
|
|
227
|
+
RIGHT: ${{ steps.right.outputs.status }}
|
|
228
|
+
run: |
|
|
229
|
+
test "$WRONG" = failed
|
|
230
|
+
test "$RIGHT" = success
|
|
231
|
+
test ! -e wrong-restore/notes.txt
|
|
232
|
+
python tests/e2e/scripts/compare.py data right-restore notes.txt
|
|
233
|
+
|
|
234
|
+
# --- signing --------------------------------------------------------
|
|
235
|
+
- name: Create keys
|
|
236
|
+
run: batchvault keygen --private signing.key --public signing.pub -q
|
|
237
|
+
|
|
238
|
+
- name: Pack with a signed manifest
|
|
239
|
+
uses: ./
|
|
240
|
+
with:
|
|
241
|
+
files: data/notes.txt
|
|
242
|
+
output: signed
|
|
243
|
+
engine: native
|
|
244
|
+
sign-key: signing.key
|
|
245
|
+
|
|
246
|
+
- name: Verify the signature
|
|
247
|
+
id: signed
|
|
248
|
+
uses: ./
|
|
249
|
+
with:
|
|
250
|
+
command: verify
|
|
251
|
+
source: signed
|
|
252
|
+
public-key: signing.pub
|
|
253
|
+
|
|
254
|
+
- name: Tamper with the manifest
|
|
255
|
+
run: |
|
|
256
|
+
python - <<'PY'
|
|
257
|
+
from pathlib import Path
|
|
258
|
+
path = Path("signed/manifest.json")
|
|
259
|
+
path.write_text(path.read_text(encoding="utf-8").replace("notes.txt", "other.txt", 1), encoding="utf-8")
|
|
260
|
+
PY
|
|
261
|
+
|
|
262
|
+
- name: Verify the tampered manifest
|
|
263
|
+
id: tampered
|
|
264
|
+
uses: ./
|
|
265
|
+
with:
|
|
266
|
+
command: verify
|
|
267
|
+
source: signed
|
|
268
|
+
public-key: signing.pub
|
|
269
|
+
fail-on-error: "false"
|
|
270
|
+
|
|
271
|
+
- name: The signature check caught the change
|
|
272
|
+
env:
|
|
273
|
+
GOOD: ${{ steps.signed.outputs.status }}
|
|
274
|
+
BAD: ${{ steps.tampered.outputs.status }}
|
|
275
|
+
run: |
|
|
276
|
+
test "$GOOD" = success
|
|
277
|
+
test "$BAD" = failed
|
|
278
|
+
|
|
279
|
+
# --- failures -------------------------------------------------------
|
|
280
|
+
- name: Pack a file that does not exist
|
|
281
|
+
id: missing
|
|
282
|
+
continue-on-error: true
|
|
283
|
+
uses: ./
|
|
284
|
+
with:
|
|
285
|
+
files: data/does-not-exist.txt
|
|
286
|
+
output: nope
|
|
287
|
+
|
|
288
|
+
- name: Pack a missing file without failing the step
|
|
289
|
+
id: missing-quiet
|
|
290
|
+
uses: ./
|
|
291
|
+
with:
|
|
292
|
+
files: data/does-not-exist.txt
|
|
293
|
+
output: nope
|
|
294
|
+
fail-on-error: "false"
|
|
295
|
+
|
|
296
|
+
- name: Failures are reported
|
|
297
|
+
env:
|
|
298
|
+
OUTCOME: ${{ steps.missing.outcome }}
|
|
299
|
+
STATUS: ${{ steps.missing-quiet.outputs.status }}
|
|
300
|
+
run: |
|
|
301
|
+
test "$OUTCOME" = failure
|
|
302
|
+
test "$STATUS" = failed
|
|
303
|
+
test ! -e nope
|
|
304
|
+
|
|
305
|
+
# --- 7-Zip (only where the runner has it) ---------------------------
|
|
306
|
+
- name: Detect 7-Zip
|
|
307
|
+
id: sevenzip
|
|
308
|
+
run: |
|
|
309
|
+
if command -v 7zz >/dev/null || command -v 7z >/dev/null || [ -f "/c/Program Files/7-Zip/7z.exe" ]; then
|
|
310
|
+
echo "found=true" >> "$GITHUB_OUTPUT"
|
|
311
|
+
else
|
|
312
|
+
echo "found=false" >> "$GITHUB_OUTPUT"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
- name: Pack with 7-Zip
|
|
316
|
+
if: steps.sevenzip.outputs.found == 'true'
|
|
317
|
+
id: sz-pack
|
|
318
|
+
uses: ./
|
|
319
|
+
with:
|
|
320
|
+
files: |
|
|
321
|
+
data/report.bin
|
|
322
|
+
data/notes.txt
|
|
323
|
+
output: sz-archives
|
|
324
|
+
engine: 7zip
|
|
325
|
+
volume-size: 256k
|
|
326
|
+
verify: "true"
|
|
327
|
+
|
|
328
|
+
- name: Unpack the 7-Zip archives
|
|
329
|
+
if: steps.sevenzip.outputs.found == 'true'
|
|
330
|
+
uses: ./
|
|
331
|
+
with:
|
|
332
|
+
command: unpack
|
|
333
|
+
source: sz-archives
|
|
334
|
+
output: sz-restored
|
|
335
|
+
|
|
336
|
+
- name: 7-Zip round trip is identical
|
|
337
|
+
if: steps.sevenzip.outputs.found == 'true'
|
|
338
|
+
run: python tests/e2e/scripts/compare.py data sz-restored report.bin notes.txt
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lint-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ruff:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
|
|
21
|
+
- uses: astral-sh/setup-uv@v7
|
|
22
|
+
|
|
23
|
+
- name: Install
|
|
24
|
+
run: uv sync --python 3.11
|
|
25
|
+
|
|
26
|
+
- name: Ruff
|
|
27
|
+
run: uv run ruff check .
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a version tag such as v0.2.0 is pushed.
|
|
4
|
+
# Uses PyPI trusted publishing: register this repository and the "pypi"
|
|
5
|
+
# environment as a trusted publisher in the PyPI project settings.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*.*.*"]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check:
|
|
15
|
+
name: Check version and tests
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
|
|
20
|
+
- uses: astral-sh/setup-uv@v7
|
|
21
|
+
|
|
22
|
+
- name: Tag must match the package version
|
|
23
|
+
shell: bash
|
|
24
|
+
run: |
|
|
25
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
26
|
+
project=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
27
|
+
module=$(python3 -c "import re; print(re.search(r'__version__ = \"(.+?)\"', open('src/batchvault/__init__.py').read()).group(1))")
|
|
28
|
+
echo "tag=$tag pyproject=$project __init__=$module"
|
|
29
|
+
if [ "$tag" != "$project" ] || [ "$tag" != "$module" ]; then
|
|
30
|
+
echo "::error::Tag v$tag must match the version in pyproject.toml ($project) and src/batchvault/__init__.py ($module)."
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
- name: Install
|
|
35
|
+
run: uv sync --python 3.11
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: uv run ruff check .
|
|
39
|
+
|
|
40
|
+
- name: Unit tests
|
|
41
|
+
run: uv run pytest -m "not integration and not e2e"
|
|
42
|
+
|
|
43
|
+
- name: Integration tests
|
|
44
|
+
run: uv run pytest -m integration
|
|
45
|
+
|
|
46
|
+
e2e:
|
|
47
|
+
name: End-to-end tests
|
|
48
|
+
uses: ./.github/workflows/e2e.yml
|
|
49
|
+
|
|
50
|
+
publish:
|
|
51
|
+
name: Build and publish
|
|
52
|
+
needs: [check, e2e]
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
environment: pypi
|
|
55
|
+
permissions:
|
|
56
|
+
contents: write
|
|
57
|
+
id-token: write
|
|
58
|
+
attestations: write
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v7
|
|
61
|
+
|
|
62
|
+
- uses: astral-sh/setup-uv@v7
|
|
63
|
+
|
|
64
|
+
- name: Build
|
|
65
|
+
run: uv build
|
|
66
|
+
|
|
67
|
+
- name: Attest build provenance
|
|
68
|
+
uses: actions/attest-build-provenance@v2
|
|
69
|
+
with:
|
|
70
|
+
subject-path: dist/*
|
|
71
|
+
|
|
72
|
+
- name: Publish to PyPI
|
|
73
|
+
run: uv publish --trusted-publishing always
|
|
74
|
+
|
|
75
|
+
- name: GitHub release
|
|
76
|
+
uses: softprops/action-gh-release@v3
|
|
77
|
+
with:
|
|
78
|
+
generate_release_notes: true
|
|
79
|
+
files: dist/*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: tests-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python }}
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
# Python 3.15 may still be a pre-release; don't let it block the build.
|
|
20
|
+
continue-on-error: ${{ matrix.python == '3.15' }}
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
include:
|
|
25
|
+
- { os: ubuntu-latest, python: "3.11" }
|
|
26
|
+
- { os: ubuntu-latest, python: "3.12" }
|
|
27
|
+
- { os: ubuntu-latest, python: "3.13" }
|
|
28
|
+
- { os: ubuntu-latest, python: "3.14" }
|
|
29
|
+
- { os: ubuntu-latest, python: "3.15" }
|
|
30
|
+
- { os: windows-latest, python: "3.11" }
|
|
31
|
+
- { os: macos-latest, python: "3.11" }
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
|
|
36
|
+
- uses: astral-sh/setup-uv@v7
|
|
37
|
+
|
|
38
|
+
- name: Install
|
|
39
|
+
run: uv sync --python ${{ matrix.python }}
|
|
40
|
+
|
|
41
|
+
- name: Unit tests
|
|
42
|
+
run: uv run pytest -m "not integration and not e2e" -n auto
|
|
43
|
+
|
|
44
|
+
- name: Integration tests
|
|
45
|
+
run: uv run pytest -m integration -n auto
|