object-storage-proxy 0.6.0__tar.gz → 0.6.2__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.
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/workflows/ci.yml +1 -4
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.gitignore +2 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CHANGELOG.md +26 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Cargo.lock +1 -1
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Cargo.toml +2 -2
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/DEVELOP.md +160 -27
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/PKG-INFO +18 -1
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/README.md +17 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Taskfile.yml +94 -21
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/flake.nix +3 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/docker-compose.yml +1 -1
- object_storage_proxy-0.6.2/integration/test_server/.proxy.pid +1 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/bootstrap.py +3 -3
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/docker-compose.yml +18 -0
- object_storage_proxy-0.6.2/integration/test_server/minio_bootstrap.py +128 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/pyproject.toml +4 -2
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/server.py +36 -8
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/spark.py +3 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/conftest.py +357 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_advanced_objects.py +352 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_bucket_ops.py +234 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_etag.py +247 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_metadata.py +258 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_range_requests.py +240 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_spark_minio.py +99 -0
- object_storage_proxy-0.6.2/integration/test_server/tests/test_tagging.py +206 -0
- object_storage_proxy-0.6.2/integration/test_server/uv.lock +1729 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/models.rs +3 -3
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/secrets_proxy.rs +1 -1
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/signer.rs +6 -6
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/lib.rs +96 -12
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/cos_map.rs +2 -2
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/path.rs +19 -9
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/metrics.rs +2 -2
- object_storage_proxy-0.6.0/integration/test_server/.proxy.pid +0 -1
- object_storage_proxy-0.6.0/integration/test_server/tests/conftest.py +0 -209
- object_storage_proxy-0.6.0/integration/test_server/uv.lock +0 -329
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.cargo/config.toml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.env.example +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/workflows/release.yml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.pre-commit-config.yaml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CODE_OF_CONDUCT.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CONTRIBUTING.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/LICENSE +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/SECURITY.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/TODO.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/cliff.toml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/examples/minimal_server.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/flake.lock +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/logo.svg +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/request_lifecycle.svg +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/request_stages.svg +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/docker/Dockerfile +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/catalog/hive.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/hive-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/query.sql +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/catalog/hive.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/log.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/node.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/.env.example +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/garage.toml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/maven/pom.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/__init__.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_aws_cli.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_multipart.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_objects.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_presigned.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_spark.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/compose.yml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/hive.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/tpcds.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/tpch.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/log.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/node.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/hive.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/tpcds.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/tpch.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/log.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/node.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/lib/postgresql-42.7.4.jar +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/README.md +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/bucket.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/login.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/metastore.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/minio.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/runtime.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/storage.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/tiny.png +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/conf/metastore-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/docker-compose.yml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/minio.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/tpcds.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/tpch.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/log.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/node.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/hive.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/tpcds.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/tpch.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/config.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/hadoop-conf/core-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/hadoop-conf/hive-site.xml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/jvm.config +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/log.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/node.properties +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/localhost.cnf +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/pyproject.toml +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/requirements.txt +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/hmac_keystore.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/mod.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/object_storage_proxy.pyi +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/credentials.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/keystore.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/mod.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/banner.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/functions.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/mod.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/response.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/validator.rs +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/test_integration.sh +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/test_server.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/tests/__init__.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/tests/test_config.py +0 -0
- {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/uv.lock +0 -0
|
@@ -5,12 +5,38 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.2] - 2026-05-14
|
|
9
|
+
|
|
10
|
+
### Chores
|
|
11
|
+
- Cleanup gitignore
|
|
12
|
+
|
|
13
|
+
- Fix spark warning test
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
- Update CHANGELOG for v0.6.0 [skip ci]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **test**: Add minio backend integration and compatibility fixes
|
|
22
|
+
|
|
23
|
+
- **ci**: Only trigger ci on tag push (release)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Testing
|
|
27
|
+
- Add more tests, based on aws own s3 sdk tests -- garage does not implement some features, so xfail until alternative found
|
|
28
|
+
|
|
29
|
+
|
|
8
30
|
## [0.6.0] - 2026-05-13
|
|
9
31
|
|
|
10
32
|
### Added
|
|
11
33
|
- Spark streaming
|
|
12
34
|
|
|
13
35
|
|
|
36
|
+
### Chores
|
|
37
|
+
- Release v0.6.0
|
|
38
|
+
|
|
39
|
+
|
|
14
40
|
### Documentation
|
|
15
41
|
- Update README
|
|
16
42
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "object-storage-proxy"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2"
|
|
4
4
|
edition = "2024"
|
|
5
|
-
description = "A fast in-process reverse proxy for AWS S3
|
|
5
|
+
description = "A fast in-process reverse proxy for AWS S3, IBM COS, and other object storage providers, with a Python interface for auth and credential management."
|
|
6
6
|
license = "MIT"
|
|
7
7
|
homepage = "https://osp.flexworks.eu"
|
|
8
8
|
repository = "https://github.com/opensourceworks-org/object-storage-proxy"
|
|
@@ -18,20 +18,21 @@
|
|
|
18
18
|
| `task test:rust` | Rust unit tests via `cargo nextest` |
|
|
19
19
|
| `task test:rust:cargo` | Rust unit tests via plain `cargo test` |
|
|
20
20
|
| `task test:integration` | run `test_integration.sh` |
|
|
21
|
-
| `task test` | build
|
|
21
|
+
| `task test` | build -> `test:rust` -> `test:integration` |
|
|
22
22
|
| `task fmt` | `cargo fmt` |
|
|
23
23
|
| `task lint` | `cargo clippy -- -D warnings` |
|
|
24
|
-
| `task wheel` | debug wheel
|
|
25
|
-
| `task wheel:release` | release wheel
|
|
24
|
+
| `task wheel` | debug wheel -> `target/wheels/` |
|
|
25
|
+
| `task wheel:release` | release wheel -> `target/wheels/` |
|
|
26
26
|
| `task clean` | remove Rust artefacts and `.venv` |
|
|
27
27
|
| `task clean:wheels` | remove `target/wheels/` only |
|
|
28
|
-
| `task integration:run` | automated integration test: up
|
|
29
|
-
| `task integration:up` | start Garage + bootstrap + OSP proxy |
|
|
30
|
-
| `task integration:down` | stop proxy + stop Garage |
|
|
28
|
+
| `task integration:run` | automated integration test: up -> test -> down |
|
|
29
|
+
| `task integration:up` | start Garage + MinIO + bootstrap both + OSP proxy |
|
|
30
|
+
| `task integration:down` | stop proxy + stop Garage + MinIO |
|
|
31
|
+
| `task integration:bootstrap` | bootstrap both backends (write `.env` + `.env.minio`) |
|
|
31
32
|
| `task integration:test` | run pytest suite (excludes Spark) |
|
|
32
|
-
| `task integration:test:spark` |
|
|
33
|
-
| `task integration:test:
|
|
34
|
-
| `task integration:test:spark` |
|
|
33
|
+
| `task integration:test:spark` | Spark tests against all backends (Garage + MinIO) |
|
|
34
|
+
| `task integration:test:spark:garage` | Spark tests against Garage only |
|
|
35
|
+
| `task integration:test:spark:minio` | Spark tests against MinIO only |
|
|
35
36
|
| `task integration:test:all` | run full suite including Spark tests |
|
|
36
37
|
| `task hooks:install` | install (or re-install) the pre-commit git hooks |
|
|
37
38
|
| `task hooks:run` | run all pre-commit checks against every file |
|
|
@@ -131,13 +132,13 @@ pip install target/wheels/object_storage_proxy-*.whl
|
|
|
131
132
|
Copy `.env.example` to `.env` (if provided) or export the variables manually, then run:
|
|
132
133
|
|
|
133
134
|
```bash
|
|
134
|
-
task run # dev build
|
|
135
|
-
task run:release # release build
|
|
135
|
+
task run # dev build -> start test_server.py
|
|
136
|
+
task run:release # release build -> start test_server.py
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
The server listens on:
|
|
139
|
-
- HTTP
|
|
140
|
-
- HTTPS
|
|
140
|
+
- HTTP -> `0.0.0.0:6190`
|
|
141
|
+
- HTTPS -> `0.0.0.0:8443`
|
|
141
142
|
|
|
142
143
|
---
|
|
143
144
|
|
|
@@ -146,7 +147,7 @@ The server listens on:
|
|
|
146
147
|
```bash
|
|
147
148
|
task test:rust # Rust unit tests via cargo nextest (recommended)
|
|
148
149
|
task test:rust:cargo # Rust unit tests via plain cargo test
|
|
149
|
-
task test # full suite: build
|
|
150
|
+
task test # full suite: build -> rust tests -> integration tests
|
|
150
151
|
```
|
|
151
152
|
|
|
152
153
|
`cargo nextest` runs tests in parallel and gives richer output. Install it once with:
|
|
@@ -311,7 +312,12 @@ Pushing a `v*` tag triggers `.github/workflows/release.yml`, which:
|
|
|
311
312
|
|
|
312
313
|
## Integration testing
|
|
313
314
|
|
|
314
|
-
The integration test suite runs OSP against
|
|
315
|
+
The integration test suite runs OSP against two real S3-compatible storage backends inside Docker:
|
|
316
|
+
|
|
317
|
+
- **[Garage](https://garagehq.deuxfleurs.fr/)** — a self-hosted S3-compatible store; the primary test target.
|
|
318
|
+
- **[MinIO](https://min.io/)** — used to verify compatibility with stricter S3 API enforcement (see [S3 API compliance](#s3-api-compliance-differences) below).
|
|
319
|
+
|
|
320
|
+
All tests live under `integration/test_server/tests/` and use pytest + boto3.
|
|
315
321
|
|
|
316
322
|
### Prerequisites
|
|
317
323
|
|
|
@@ -322,7 +328,7 @@ The integration test suite runs OSP against a real [Garage](https://garagehq.deu
|
|
|
322
328
|
|
|
323
329
|
```bash
|
|
324
330
|
task integration:setup # install test Python deps (once)
|
|
325
|
-
task integration:run # garage up
|
|
331
|
+
task integration:run # garage up -> bootstrap -> proxy -> test -> teardown
|
|
326
332
|
```
|
|
327
333
|
|
|
328
334
|
`integration:run` tears everything down even if tests fail.
|
|
@@ -334,10 +340,17 @@ Bring the stack up once and iterate on tests without restarting:
|
|
|
334
340
|
```bash
|
|
335
341
|
task integration:setup # install test Python deps (once)
|
|
336
342
|
task integration:garage:up # start Garage container
|
|
337
|
-
task integration:
|
|
343
|
+
task integration:minio:up # start MinIO container
|
|
344
|
+
task integration:bootstrap # create buckets + keys, write .env + .env.minio
|
|
338
345
|
task integration:server:start # start the OSP proxy in the background
|
|
339
346
|
```
|
|
340
347
|
|
|
348
|
+
Or use the single convenience shortcut that does all of the above:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
task integration:up
|
|
352
|
+
```
|
|
353
|
+
|
|
341
354
|
Run the tests:
|
|
342
355
|
|
|
343
356
|
```bash
|
|
@@ -357,22 +370,31 @@ task integration:garage:destroy # also wipe Garage data volumes
|
|
|
357
370
|
| Task | Description |
|
|
358
371
|
|------|-------------|
|
|
359
372
|
| `integration:setup` | `uv sync` in `integration/test_server/` |
|
|
360
|
-
| `integration:up` | Garage up
|
|
361
|
-
| `integration:down` | Stop proxy
|
|
362
|
-
| `integration:
|
|
373
|
+
| `integration:up` | Garage + MinIO up -> bootstrap both -> proxy start |
|
|
374
|
+
| `integration:down` | Stop proxy -> stop Garage + MinIO |
|
|
375
|
+
| `integration:bootstrap` | Bootstrap both backends (write `.env` + `.env.minio`) |
|
|
376
|
+
| `integration:run` | Automated: up -> test -> down (teardown on failure too) |
|
|
363
377
|
| `integration:garage:up` | Start the Garage Docker container |
|
|
364
|
-
| `integration:garage:down` | Stop and remove the container |
|
|
378
|
+
| `integration:garage:down` | Stop and remove the Garage container |
|
|
365
379
|
| `integration:garage:destroy` | Stop container **and** remove data volumes |
|
|
366
380
|
| `integration:garage:bootstrap` | Create bucket + HMAC key in Garage, write `.env` |
|
|
367
381
|
| `integration:garage:logs` | Follow Garage container logs |
|
|
368
382
|
| `integration:garage:status` | Query Garage cluster status via admin API |
|
|
369
|
-
| `integration:
|
|
383
|
+
| `integration:minio:up` | Start the MinIO Docker container |
|
|
384
|
+
| `integration:minio:down` | Stop and remove the MinIO container |
|
|
385
|
+
| `integration:minio:destroy` | Stop container **and** remove data volumes |
|
|
386
|
+
| `integration:minio:bootstrap` | Create MinIO bucket, write `.env.minio` |
|
|
387
|
+
| `integration:server:start` | Start OSP proxy in the background (logs -> `proxy.log`) |
|
|
370
388
|
| `integration:server:stop` | Stop the background proxy |
|
|
371
389
|
| `integration:server:logs` | Tail the proxy log |
|
|
372
|
-
| `integration:test` | Run pytest suite against the running environment |
|
|
390
|
+
| `integration:test` | Run pytest suite against the running environment (excludes Spark) |
|
|
373
391
|
| `integration:test:fast` | Same, with `-x` (stop on first failure) |
|
|
374
|
-
| `integration:test:spark` |
|
|
375
|
-
| `integration:test:spark:
|
|
392
|
+
| `integration:test:spark` | Spark tests against all backends (Garage + MinIO) |
|
|
393
|
+
| `integration:test:spark:garage` | Spark tests against Garage only |
|
|
394
|
+
| `integration:test:spark:garage:fast` | Same, with `-x` |
|
|
395
|
+
| `integration:test:spark:minio` | Spark tests against MinIO only |
|
|
396
|
+
| `integration:test:spark:minio:fast` | Same, with `-x` |
|
|
397
|
+
| `integration:test:spark:fast` | Spark tests against all backends, stop on first failure |
|
|
376
398
|
| `integration:test:all` | Run full suite including Spark tests |
|
|
377
399
|
|
|
378
400
|
### What the tests cover
|
|
@@ -383,7 +405,38 @@ task integration:garage:destroy # also wipe Garage data volumes
|
|
|
383
405
|
| `tests/test_multipart.py` | CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload, ListParts |
|
|
384
406
|
| `tests/test_presigned.py` | Presigned GET/PUT, expiry enforcement, repeated-use limiting |
|
|
385
407
|
| `tests/test_aws_cli.py` | `aws s3 ls`, `cp`, `sync`, `rm` via subprocess |
|
|
386
|
-
| `tests/
|
|
408
|
+
| `tests/test_range_requests.py` | Byte-range GET (single, multi, suffix, full), `206 Partial Content`, conditional GET headers (`If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since`) |
|
|
409
|
+
| `tests/test_metadata.py` | Custom `x-amz-meta-*` headers, `Content-Type`, `Cache-Control`, `Content-Disposition`, `Content-Encoding`, copy metadata directives (`COPY` / `REPLACE`) |
|
|
410
|
+
| `tests/test_etag.py` | ETag format for single-part and multipart objects, ETag preservation through CopyObject, checksum header tolerance |
|
|
411
|
+
| `tests/test_tagging.py` | `PutObjectTagging`, `GetObjectTagging`, `DeleteObjectTagging` — `xfail` for Garage (not implemented), **expected to pass** for MinIO |
|
|
412
|
+
| `tests/test_bucket_ops.py` | HeadBucket, ListBuckets, GetBucketLocation, ListObjectsV1/V2 extended params, ListMultipartUploads |
|
|
413
|
+
| `tests/test_advanced_objects.py` | DeleteObjects (quiet/mixed), UploadPartCopy (full source + byte-range), presigned HEAD/DELETE/GET-with-range, `response-*` query overrides, 12 MB streaming PUT |
|
|
414
|
+
| `tests/test_spark.py` | Spark s3a read/write via OSP → **Garage**: Parquet, JSON, overwrite, empty DataFrame, large DataFrame (10 000 rows) |
|
|
415
|
+
| `tests/test_spark_minio.py` | Same five tests via OSP → **MinIO** — also serves as a regression guard for `Content-MD5` forwarding on `DeleteObjects` (see [S3 API compliance](#s3-api-compliance-differences)) |
|
|
416
|
+
|
|
417
|
+
### Known backend limitations
|
|
418
|
+
|
|
419
|
+
The following S3 operations are known to behave differently across backends. The proxy forwards all requests correctly — the limitations are entirely in the storage backends. Because the test suite runs against **both** Garage and MinIO (parametrized as `[garage]` and `[minio]`), each limitation is isolated to the relevant backend's run.
|
|
420
|
+
|
|
421
|
+
**Garage v1.0.1 — xfail on `[garage]`, passes on `[minio]`:**
|
|
422
|
+
|
|
423
|
+
| Operation | Garage symptom |
|
|
424
|
+
|-----------|---------------|
|
|
425
|
+
| `PutObjectTagging` | `NotImplemented` |
|
|
426
|
+
| `GetObjectTagging` | `NotImplemented` |
|
|
427
|
+
| `DeleteObjectTagging` | `NotImplemented` |
|
|
428
|
+
| `If-Match` enforcement on `GET` | Returns `200` instead of `412` |
|
|
429
|
+
| `If-Unmodified-Since` enforcement on `GET` | Returns `200` instead of `412` |
|
|
430
|
+
|
|
431
|
+
> `If-None-Match` (→ `304`) and `If-Modified-Since` (→ `304`) **are** enforced correctly by both backends.
|
|
432
|
+
|
|
433
|
+
**MinIO — xfail on `[minio]`, passes on `[garage]`:**
|
|
434
|
+
|
|
435
|
+
| Operation | MinIO symptom |
|
|
436
|
+
|-----------|---------------|
|
|
437
|
+
| `ListMultipartUploads` with `Prefix` ending in `/` | Returns empty `Uploads` list; using the full key as `Prefix` (without trailing slash) works correctly |
|
|
438
|
+
|
|
439
|
+
All xfail markers use `strict=False` so the suite stays green and will automatically promote to a pass if the backend is upgraded.
|
|
387
440
|
|
|
388
441
|
### Spark tests
|
|
389
442
|
|
|
@@ -481,8 +534,88 @@ Key Hadoop S3A settings applied:
|
|
|
481
534
|
| 3901 | Garage RPC |
|
|
482
535
|
| 3903 | Garage admin API |
|
|
483
536
|
| 6190 | OSP proxy (S3 frontend) |
|
|
537
|
+
| 9000 | MinIO S3 API |
|
|
538
|
+
| 9001 | MinIO web console |
|
|
484
539
|
| 9091 | OSP Prometheus metrics |
|
|
485
540
|
|
|
486
541
|
### Environment
|
|
487
542
|
|
|
488
|
-
`bootstrap.py` writes `integration/test_server/.env` with the generated Garage credentials.
|
|
543
|
+
`bootstrap.py` writes `integration/test_server/.env` with the generated Garage credentials.
|
|
544
|
+
`minio_bootstrap.py` writes `integration/test_server/.env.minio` with the MinIO credentials.
|
|
545
|
+
Both files are loaded automatically by `server.py` and the pytest fixtures. Both are gitignored — never commit them.
|
|
546
|
+
|
|
547
|
+
Tests in `test_spark_minio.py` are skipped automatically when `.env.minio` is absent, so the Garage-only suite always works without MinIO running.
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## S3 API compliance differences
|
|
552
|
+
|
|
553
|
+
Different S3-compatible backends vary in how strictly they follow the AWS S3 specification. OSP is tested against both Garage and MinIO to catch these differences early.
|
|
554
|
+
|
|
555
|
+
| Operation | AWS spec | Garage v1.0.1 | MinIO | Test status |
|
|
556
|
+
|-----------|----------|---------------|-------|-------------|
|
|
557
|
+
| `DeleteObjects` — `Content-MD5` header | **Required** | Accepted without it (lenient) | Rejected without it (`400 InvalidRequest`) | passes on both (see note below) |
|
|
558
|
+
| `x-amz-tagging-directive` on `CopyObject` | `COPY` or `REPLACE` | N/A (tagging not implemented) | ✅ enforced | passes on MinIO |
|
|
559
|
+
| `PutObjectTagging` / `GetObjectTagging` / `DeleteObjectTagging` | Supported | `NotImplemented` | ✅ Supported | `xfail` Garage, passes MinIO |
|
|
560
|
+
| `If-Match` / `If-Unmodified-Since` on `GET` | Must return `412` when condition fails | Returns `200` (header ignored) | ✅ Returns `412` | `xfail` Garage, passes MinIO |
|
|
561
|
+
| `ListMultipartUploads` with `Prefix` ending in `/` | Returns matching uploads | ✅ works | Returns empty list | passes Garage, `xfail` MinIO |
|
|
562
|
+
|
|
563
|
+
### botocore ≥ 1.43 and Content-MD5
|
|
564
|
+
|
|
565
|
+
botocore 1.43 changed `DeleteObjects` to send `x-amz-checksum-crc32` instead of `Content-MD5` for body integrity. This behaviour **cannot** be overridden via the `request_checksum_calculation` config option — the old header is simply never emitted regardless of settings.
|
|
566
|
+
|
|
567
|
+
MinIO still **requires** `Content-MD5` on `DeleteObjects` and rejects requests without it with `400 MissingContentMD5`. To keep the test suite working correctly against both backends, `conftest.py` injects the header via a botocore event hook that runs _before_ SigV4 signing (so it appears in `SignedHeaders`):
|
|
568
|
+
|
|
569
|
+
```python
|
|
570
|
+
def _register_delete_objects_md5(client) -> None:
|
|
571
|
+
def _inject(request, **kwargs):
|
|
572
|
+
body = request.body
|
|
573
|
+
if body is None:
|
|
574
|
+
return
|
|
575
|
+
if isinstance(body, str):
|
|
576
|
+
body = body.encode("utf-8")
|
|
577
|
+
request.headers["Content-MD5"] = base64.b64encode(
|
|
578
|
+
hashlib.md5(body).digest()
|
|
579
|
+
).decode()
|
|
580
|
+
|
|
581
|
+
client.meta.events.register("before-sign.s3.DeleteObjects", _inject)
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
OSP itself forwards `Content-MD5` unchanged when it is present — it is in the upstream allowlist and is never stripped. If you use boto3 ≥ 1.43 in your own application against a MinIO backend through OSP, you will need the same hook.
|
|
585
|
+
|
|
586
|
+
### Internal proxy fixes discovered during dual-backend testing
|
|
587
|
+
|
|
588
|
+
These bugs in OSP were found and fixed while adding MinIO to the test matrix.
|
|
589
|
+
|
|
590
|
+
#### Bare sub-resource query keys (`?delete`, `?uploads`, `?tagging`)
|
|
591
|
+
|
|
592
|
+
The S3 protocol uses bare query parameters (no `=value`) as sub-resource identifiers. OSP's query parser previously required every key to have an `=` sign, causing `DeleteObjects` (`?delete`), `ListMultipartUploads` (`?uploads`), and `PutObjectTagging` (`?tagging`) to return `500`. Fixed in `src/parsers/path.rs`:
|
|
593
|
+
|
|
594
|
+
```rust
|
|
595
|
+
// Bare sub-resource key with no value (e.g. "delete", "uploads", "tagging").
|
|
596
|
+
let (input, key) = map_res(take_until_either("&"), decode_segment).parse(input)?;
|
|
597
|
+
Ok((input, (key, String::new())))
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
#### Bucket-root trailing slash (path-style addressing)
|
|
601
|
+
|
|
602
|
+
For path-style backends, bucket-level requests (`GET /bucket?uploads&prefix=…`) were forwarded upstream as `GET /bucket/?uploads&prefix=…` (extra trailing slash), causing signature mismatches. Fixed in `upstream_request_filter` in `src/lib.rs`:
|
|
603
|
+
|
|
604
|
+
```rust
|
|
605
|
+
// Strip trailing slash for bucket-root requests in path-style mode.
|
|
606
|
+
let u_url = if my_updated_url == "/" {
|
|
607
|
+
format!("/{}", bucket)
|
|
608
|
+
} else {
|
|
609
|
+
format!("/{}{}", bucket, my_updated_url)
|
|
610
|
+
};
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### `x-amz-tagging-directive` stripped by the proxy
|
|
614
|
+
|
|
615
|
+
`CopyObject` with `TaggingDirective: REPLACE` requires `x-amz-tagging-directive` to reach the backend. It was not in OSP's upstream header allowlist, so it was silently dropped. Fixed by adding it alongside `x-amz-tagging` in `src/lib.rs`.
|
|
616
|
+
|
|
617
|
+
#### Separate bucket names required per backend
|
|
618
|
+
|
|
619
|
+
Both backends initially used `test-bucket`, causing MinIO's `cos_map` entry to silently overwrite Garage's. All requests then routed to MinIO regardless of the parametrized backend. Fixed by giving MinIO a distinct name (`test-bucket-minio`) in `minio_bootstrap.py`.
|
|
620
|
+
|
|
621
|
+
> **cos_map constraint:** each key in `cos_map` must be globally unique. If you need the same logical bucket name on multiple backends, use distinct proxy-side names and map them in your `authorize` callback.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: object-storage-proxy
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
@@ -53,6 +53,7 @@ This proxy solves that by:
|
|
|
53
53
|
## Features
|
|
54
54
|
|
|
55
55
|
- Compatible with any AWS S3-compatible client: aws-cli, boto3, polars, spark, datafusion, presto, trino, ...
|
|
56
|
+
- Normalises differences between S3-compatible backends so clients work regardless of whether the backend is AWS S3, MinIO, Garage, or IBM COS (see [Backend compatibility](#backend-compatibility) below).
|
|
56
57
|
- Decouples frontend authentication (what the client sends) from backend authentication (what the storage expects).
|
|
57
58
|
- Python callables for credential fetching, HMAC key lookup, and per-request authorization.
|
|
58
59
|
- TTL-based credential and authorization caching.
|
|
@@ -215,6 +216,22 @@ def lookup_secret(access_key: str) -> str | None: ...
|
|
|
215
216
|
def authorize(token: str, bucket: str, request: dict | None = None) -> bool: ...
|
|
216
217
|
```
|
|
217
218
|
|
|
219
|
+
## Backend compatibility
|
|
220
|
+
|
|
221
|
+
S3-compatible backends differ in how strictly they follow the AWS S3 specification. OSP irons out these differences so clients don't need to care which backend is underneath.
|
|
222
|
+
|
|
223
|
+
| Behaviour | AWS S3 spec | Garage | MinIO | OSP handling |
|
|
224
|
+
|-----------|-------------|--------|-------|-------------|
|
|
225
|
+
| `Content-MD5` on `DeleteObjects` | **Required** | Accepted without it (lenient) | Enforced (`400` if missing) | Forwarded when present; test suite injects it because botocore ≥ 1.43 no longer sends it by default |
|
|
226
|
+
| `x-amz-tagging-directive` on `CopyObject` | `COPY` or `REPLACE` | N/A (tagging not implemented) | ✅ enforced | Header is in OSP's forwarding allowlist — was previously stripped |
|
|
227
|
+
| `PutObjectTagging` / `GetObjectTagging` | Supported | `NotImplemented` | ✅ | Forwarded; backend limitation is transparent |
|
|
228
|
+
| `If-Match` / `If-Unmodified-Since` on `GET` | Must return `412` | Returns `200` (header ignored) | ✅ Returns `412` | Forwarded; backend limitation is transparent |
|
|
229
|
+
| `ListMultipartUploads` with `Prefix` ending in `/` | Returns matching uploads | ✅ works | Returns empty list (MinIO bug) | Forwarded; MinIO limitation documented as `xfail` in the test suite |
|
|
230
|
+
|
|
231
|
+
> **botocore ≥ 1.43 note:** Recent versions of boto3 switched from `Content-MD5` to `x-amz-checksum-crc32` for body integrity on `DeleteObjects`, regardless of the `request_checksum_calculation` setting. `Content-MD5` is still required by MinIO. If you use boto3 ≥ 1.43 directly against MinIO through OSP you may need to inject `Content-MD5` manually via a `before-sign` event hook — see [DEVELOP.md](DEVELOP.md#botocore-43-and-content-md5) for details and example code.
|
|
232
|
+
|
|
233
|
+
The integration test suite covers all of the above: every test runs parametrized over both Garage and MinIO backends, so regressions surface immediately. See [DEVELOP.md](DEVELOP.md#s3-api-compliance-differences) for the full compliance table and the internal proxy fixes that enable it.
|
|
234
|
+
|
|
218
235
|
## Prometheus metrics
|
|
219
236
|
|
|
220
237
|
The proxy ships with a built-in Prometheus scrape endpoint. Set `metrics_port` to enable it:
|
|
@@ -33,6 +33,7 @@ This proxy solves that by:
|
|
|
33
33
|
## Features
|
|
34
34
|
|
|
35
35
|
- Compatible with any AWS S3-compatible client: aws-cli, boto3, polars, spark, datafusion, presto, trino, ...
|
|
36
|
+
- Normalises differences between S3-compatible backends so clients work regardless of whether the backend is AWS S3, MinIO, Garage, or IBM COS (see [Backend compatibility](#backend-compatibility) below).
|
|
36
37
|
- Decouples frontend authentication (what the client sends) from backend authentication (what the storage expects).
|
|
37
38
|
- Python callables for credential fetching, HMAC key lookup, and per-request authorization.
|
|
38
39
|
- TTL-based credential and authorization caching.
|
|
@@ -195,6 +196,22 @@ def lookup_secret(access_key: str) -> str | None: ...
|
|
|
195
196
|
def authorize(token: str, bucket: str, request: dict | None = None) -> bool: ...
|
|
196
197
|
```
|
|
197
198
|
|
|
199
|
+
## Backend compatibility
|
|
200
|
+
|
|
201
|
+
S3-compatible backends differ in how strictly they follow the AWS S3 specification. OSP irons out these differences so clients don't need to care which backend is underneath.
|
|
202
|
+
|
|
203
|
+
| Behaviour | AWS S3 spec | Garage | MinIO | OSP handling |
|
|
204
|
+
|-----------|-------------|--------|-------|-------------|
|
|
205
|
+
| `Content-MD5` on `DeleteObjects` | **Required** | Accepted without it (lenient) | Enforced (`400` if missing) | Forwarded when present; test suite injects it because botocore ≥ 1.43 no longer sends it by default |
|
|
206
|
+
| `x-amz-tagging-directive` on `CopyObject` | `COPY` or `REPLACE` | N/A (tagging not implemented) | ✅ enforced | Header is in OSP's forwarding allowlist — was previously stripped |
|
|
207
|
+
| `PutObjectTagging` / `GetObjectTagging` | Supported | `NotImplemented` | ✅ | Forwarded; backend limitation is transparent |
|
|
208
|
+
| `If-Match` / `If-Unmodified-Since` on `GET` | Must return `412` | Returns `200` (header ignored) | ✅ Returns `412` | Forwarded; backend limitation is transparent |
|
|
209
|
+
| `ListMultipartUploads` with `Prefix` ending in `/` | Returns matching uploads | ✅ works | Returns empty list (MinIO bug) | Forwarded; MinIO limitation documented as `xfail` in the test suite |
|
|
210
|
+
|
|
211
|
+
> **botocore ≥ 1.43 note:** Recent versions of boto3 switched from `Content-MD5` to `x-amz-checksum-crc32` for body integrity on `DeleteObjects`, regardless of the `request_checksum_calculation` setting. `Content-MD5` is still required by MinIO. If you use boto3 ≥ 1.43 directly against MinIO through OSP you may need to inject `Content-MD5` manually via a `before-sign` event hook — see [DEVELOP.md](DEVELOP.md#botocore-43-and-content-md5) for details and example code.
|
|
212
|
+
|
|
213
|
+
The integration test suite covers all of the above: every test runs parametrized over both Garage and MinIO backends, so regressions surface immediately. See [DEVELOP.md](DEVELOP.md#s3-api-compliance-differences) for the full compliance table and the internal proxy fixes that enable it.
|
|
214
|
+
|
|
198
215
|
## Prometheus metrics
|
|
199
216
|
|
|
200
217
|
The proxy ships with a built-in Prometheus scrape endpoint. Set `metrics_port` to enable it:
|
|
@@ -146,15 +146,23 @@ tasks:
|
|
|
146
146
|
# manual dev/testing while restarting just the proxy (or vice-versa).
|
|
147
147
|
#
|
|
148
148
|
# Quick reference:
|
|
149
|
-
# task integration:up
|
|
150
|
-
# task integration:down
|
|
151
|
-
# task integration:test
|
|
152
|
-
# task integration:run
|
|
149
|
+
# task integration:up # full stack (garage + bootstrap + proxy)
|
|
150
|
+
# task integration:down # tear everything down
|
|
151
|
+
# task integration:test # run tests against whatever is running
|
|
152
|
+
# task integration:run # automated: up -> test -> down
|
|
153
153
|
#
|
|
154
|
-
# task integration:garage:up
|
|
154
|
+
# task integration:garage:up # just Garage (stays running)
|
|
155
155
|
# task integration:garage:bootstrap # create bucket + key, write .env
|
|
156
|
-
# task integration:server:start
|
|
157
|
-
# task integration:server:stop
|
|
156
|
+
# task integration:server:start # just the OSP proxy (background)
|
|
157
|
+
# task integration:server:stop # kill the proxy
|
|
158
|
+
#
|
|
159
|
+
# task integration:minio:up # start MinIO container
|
|
160
|
+
# task integration:minio:bootstrap # create bucket, write .env.minio
|
|
161
|
+
# task integration:minio:down # stop MinIO container
|
|
162
|
+
# task integration:minio:destroy # stop MinIO AND remove volumes
|
|
163
|
+
# task integration:test:spark # Spark tests against all backends
|
|
164
|
+
# task integration:test:spark:garage # Spark tests (Garage only)
|
|
165
|
+
# task integration:test:spark:minio # Spark tests (MinIO only)
|
|
158
166
|
|
|
159
167
|
integration:setup:
|
|
160
168
|
desc: "Install Python deps for the integration test project"
|
|
@@ -170,7 +178,7 @@ tasks:
|
|
|
170
178
|
- echo "Garage starting — waiting for healthy status …"
|
|
171
179
|
- |
|
|
172
180
|
for i in $(seq 1 30); do
|
|
173
|
-
status=$(docker inspect --format='{{.State.Health.Status}}' osp-garage 2>/dev/null)
|
|
181
|
+
status=$(docker inspect --format='{{"{{"}}{{".State.Health.Status"}}{{"}}"}}' osp-garage 2>/dev/null)
|
|
174
182
|
if [ "$status" = "healthy" ]; then echo "Garage is healthy"; exit 0; fi
|
|
175
183
|
echo " waiting… ($i/30) status=$status"; sleep 2
|
|
176
184
|
done
|
|
@@ -207,14 +215,47 @@ tasks:
|
|
|
207
215
|
cmds:
|
|
208
216
|
- uv run python bootstrap.py
|
|
209
217
|
|
|
218
|
+
integration:minio:up:
|
|
219
|
+
desc: "Start the MinIO container (stays running)"
|
|
220
|
+
dir: integration/test_server
|
|
221
|
+
cmds:
|
|
222
|
+
- docker compose up -d minio
|
|
223
|
+
- echo "MinIO starting — waiting for healthy status …"
|
|
224
|
+
- |
|
|
225
|
+
for i in $(seq 1 30); do
|
|
226
|
+
status=$(docker inspect --format='{{"{{"}}{{".State.Health.Status"}}{{"}}"}}' osp-minio 2>/dev/null)
|
|
227
|
+
if [ "$status" = "healthy" ]; then echo "MinIO is healthy"; exit 0; fi
|
|
228
|
+
echo " waiting… ($i/30) status=$status"; sleep 2
|
|
229
|
+
done
|
|
230
|
+
echo "MinIO did not become healthy in time" >&2; exit 1
|
|
231
|
+
|
|
232
|
+
integration:minio:bootstrap:
|
|
233
|
+
desc: "Create MinIO bucket and write integration/test_server/.env.minio"
|
|
234
|
+
dir: integration/test_server
|
|
235
|
+
cmds:
|
|
236
|
+
- uv run python minio_bootstrap.py
|
|
237
|
+
|
|
238
|
+
integration:minio:down:
|
|
239
|
+
desc: "Stop and remove the MinIO container"
|
|
240
|
+
dir: integration/test_server
|
|
241
|
+
cmds:
|
|
242
|
+
- docker compose stop minio
|
|
243
|
+
- docker compose rm -f minio
|
|
244
|
+
|
|
245
|
+
integration:minio:destroy:
|
|
246
|
+
desc: "Stop MinIO AND remove its data volumes"
|
|
247
|
+
dir: integration/test_server
|
|
248
|
+
cmds:
|
|
249
|
+
- docker compose down -v minio
|
|
250
|
+
|
|
210
251
|
integration:server:start:
|
|
211
|
-
desc: "Start the OSP proxy in the background (logs
|
|
252
|
+
desc: "Start the OSP proxy in the background (logs -> integration/test_server/proxy.log)"
|
|
212
253
|
cmds:
|
|
213
254
|
- |
|
|
214
255
|
nohup uv run --no-sync python integration/test_server/server.py \
|
|
215
256
|
> integration/test_server/proxy.log 2>&1 &
|
|
216
|
-
echo $! > integration/test_server/.proxy.pid
|
|
217
257
|
sleep 2
|
|
258
|
+
pgrep -f "server\.py" | head -1 > integration/test_server/.proxy.pid
|
|
218
259
|
if kill -0 $(cat integration/test_server/.proxy.pid) 2>/dev/null; then
|
|
219
260
|
echo "✅ Proxy started (PID=$(cat integration/test_server/.proxy.pid))"
|
|
220
261
|
echo " logs: integration/test_server/proxy.log"
|
|
@@ -242,51 +283,83 @@ tasks:
|
|
|
242
283
|
cmds:
|
|
243
284
|
- tail -f integration/test_server/proxy.log
|
|
244
285
|
|
|
286
|
+
integration:bootstrap:
|
|
287
|
+
desc: "Bootstrap both backends: create Garage bucket+key and MinIO bucket, write .env files"
|
|
288
|
+
cmds:
|
|
289
|
+
- task: integration:garage:bootstrap
|
|
290
|
+
- task: integration:minio:bootstrap
|
|
291
|
+
|
|
245
292
|
integration:up:
|
|
246
|
-
desc: "Full environment: Garage up
|
|
293
|
+
desc: "Full environment: Garage + MinIO up -> bootstrap both -> proxy start"
|
|
247
294
|
cmds:
|
|
248
295
|
- task: integration:garage:up
|
|
249
|
-
- task: integration:
|
|
296
|
+
- task: integration:minio:up
|
|
297
|
+
- task: integration:bootstrap
|
|
250
298
|
- task: integration:server:start
|
|
251
299
|
|
|
252
300
|
integration:down:
|
|
253
|
-
desc: "Tear down: stop proxy
|
|
301
|
+
desc: "Tear down: stop proxy -> stop Garage + MinIO"
|
|
254
302
|
cmds:
|
|
255
303
|
- task: integration:server:stop
|
|
256
304
|
- task: integration:garage:down
|
|
305
|
+
- task: integration:minio:down
|
|
257
306
|
|
|
258
307
|
integration:test:
|
|
259
308
|
desc: "Run integration tests against the running environment (excludes Spark)"
|
|
260
309
|
dir: integration/test_server
|
|
261
310
|
cmds:
|
|
262
|
-
- uv run pytest tests/ -
|
|
311
|
+
- uv run pytest tests/ -q -m 'not spark and not spark_minio'
|
|
263
312
|
|
|
264
313
|
integration:test:fast:
|
|
265
314
|
desc: "Run integration tests, stop on first failure (excludes Spark)"
|
|
266
315
|
dir: integration/test_server
|
|
267
316
|
cmds:
|
|
268
|
-
- uv run pytest tests/ -
|
|
317
|
+
- uv run pytest tests/ -q -x -m 'not spark and not spark_minio'
|
|
269
318
|
|
|
270
319
|
integration:test:spark:
|
|
271
|
-
desc: "Run
|
|
320
|
+
desc: "Run Spark s3a tests against all backends (Garage + MinIO)"
|
|
321
|
+
cmds:
|
|
322
|
+
- task: integration:test:spark:garage
|
|
323
|
+
- task: integration:test:spark:minio
|
|
324
|
+
|
|
325
|
+
integration:test:spark:garage:
|
|
326
|
+
desc: "Run only the Spark s3a tests (Garage backend)"
|
|
327
|
+
dir: integration/test_server
|
|
328
|
+
cmds:
|
|
329
|
+
- uv run pytest tests/test_spark.py -q -m spark
|
|
330
|
+
|
|
331
|
+
integration:test:spark:garage:fast:
|
|
332
|
+
desc: "Run Spark tests (Garage), stop on first failure"
|
|
272
333
|
dir: integration/test_server
|
|
273
334
|
cmds:
|
|
274
|
-
- uv run pytest tests/test_spark.py -
|
|
335
|
+
- uv run pytest tests/test_spark.py -q -m spark -x
|
|
275
336
|
|
|
276
337
|
integration:test:spark:fast:
|
|
277
|
-
desc: "Run Spark tests, stop on first failure"
|
|
338
|
+
desc: "Run Spark tests against all backends, stop on first failure"
|
|
339
|
+
dir: integration/test_server
|
|
340
|
+
cmds:
|
|
341
|
+
- uv run pytest tests/test_spark.py tests/test_spark_minio.py -q -m 'spark or spark_minio' -x
|
|
342
|
+
|
|
343
|
+
integration:test:spark:minio:
|
|
344
|
+
desc: "Run only the Spark s3a tests (MinIO backend)"
|
|
345
|
+
dir: integration/test_server
|
|
346
|
+
cmds:
|
|
347
|
+
- uv run pytest tests/test_spark_minio.py -q -m spark_minio
|
|
348
|
+
|
|
349
|
+
integration:test:spark:minio:fast:
|
|
350
|
+
desc: "Run Spark tests (MinIO), stop on first failure"
|
|
278
351
|
dir: integration/test_server
|
|
279
352
|
cmds:
|
|
280
|
-
- uv run pytest tests/
|
|
353
|
+
- uv run pytest tests/test_spark_minio.py -q -m spark_minio -x
|
|
281
354
|
|
|
282
355
|
integration:test:all:
|
|
283
356
|
desc: "Run full test suite including Spark tests"
|
|
284
357
|
dir: integration/test_server
|
|
285
358
|
cmds:
|
|
286
|
-
- uv run pytest tests/ -
|
|
359
|
+
- uv run pytest tests/ -q
|
|
287
360
|
|
|
288
361
|
integration:run:
|
|
289
|
-
desc: "Automated: up
|
|
362
|
+
desc: "Automated: up -> test -> down (tears down even on failure)"
|
|
290
363
|
cmds:
|
|
291
364
|
- task: integration:up
|
|
292
365
|
- defer: {task: integration:down}
|
|
@@ -58,6 +58,9 @@
|
|
|
58
58
|
OPENSSL_DIR = "${pkgs.openssl.dev}";
|
|
59
59
|
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
|
|
60
60
|
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include";
|
|
61
|
+
# Needed for compiled Python extensions (e.g. pyzmq) that link against
|
|
62
|
+
# libstdc++.so.6 — on NixOS this is not on the default search path.
|
|
63
|
+
LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib";
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
shellHook = ''
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
246334
|