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.
Files changed (148) hide show
  1. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/workflows/ci.yml +1 -4
  2. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.gitignore +2 -0
  3. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CHANGELOG.md +26 -0
  4. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Cargo.lock +1 -1
  5. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Cargo.toml +2 -2
  6. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/DEVELOP.md +160 -27
  7. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/PKG-INFO +18 -1
  8. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/README.md +17 -0
  9. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/Taskfile.yml +94 -21
  10. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/flake.nix +3 -0
  11. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/docker-compose.yml +1 -1
  12. object_storage_proxy-0.6.2/integration/test_server/.proxy.pid +1 -0
  13. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/bootstrap.py +3 -3
  14. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/docker-compose.yml +18 -0
  15. object_storage_proxy-0.6.2/integration/test_server/minio_bootstrap.py +128 -0
  16. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/pyproject.toml +4 -2
  17. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/server.py +36 -8
  18. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/spark.py +3 -0
  19. object_storage_proxy-0.6.2/integration/test_server/tests/conftest.py +357 -0
  20. object_storage_proxy-0.6.2/integration/test_server/tests/test_advanced_objects.py +352 -0
  21. object_storage_proxy-0.6.2/integration/test_server/tests/test_bucket_ops.py +234 -0
  22. object_storage_proxy-0.6.2/integration/test_server/tests/test_etag.py +247 -0
  23. object_storage_proxy-0.6.2/integration/test_server/tests/test_metadata.py +258 -0
  24. object_storage_proxy-0.6.2/integration/test_server/tests/test_range_requests.py +240 -0
  25. object_storage_proxy-0.6.2/integration/test_server/tests/test_spark_minio.py +99 -0
  26. object_storage_proxy-0.6.2/integration/test_server/tests/test_tagging.py +206 -0
  27. object_storage_proxy-0.6.2/integration/test_server/uv.lock +1729 -0
  28. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/models.rs +3 -3
  29. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/secrets_proxy.rs +1 -1
  30. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/signer.rs +6 -6
  31. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/lib.rs +96 -12
  32. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/cos_map.rs +2 -2
  33. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/path.rs +19 -9
  34. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/metrics.rs +2 -2
  35. object_storage_proxy-0.6.0/integration/test_server/.proxy.pid +0 -1
  36. object_storage_proxy-0.6.0/integration/test_server/tests/conftest.py +0 -209
  37. object_storage_proxy-0.6.0/integration/test_server/uv.lock +0 -329
  38. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.cargo/config.toml +0 -0
  39. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.env.example +0 -0
  40. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  41. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  42. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  43. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.github/workflows/release.yml +0 -0
  44. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/.pre-commit-config.yaml +0 -0
  45. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CODE_OF_CONDUCT.md +0 -0
  46. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/CONTRIBUTING.md +0 -0
  47. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/LICENSE +0 -0
  48. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/SECURITY.md +0 -0
  49. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/TODO.md +0 -0
  50. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/cliff.toml +0 -0
  51. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/examples/minimal_server.py +0 -0
  52. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/flake.lock +0 -0
  53. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/logo.svg +0 -0
  54. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/request_lifecycle.svg +0 -0
  55. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/img/request_stages.svg +0 -0
  56. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/docker/Dockerfile +0 -0
  57. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/catalog/hive.properties +0 -0
  58. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/config.properties +0 -0
  59. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/hadoop-conf/core-site.xml +0 -0
  60. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/hadoop-conf/hive-site.xml +0 -0
  61. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/etc/jvm.config +0 -0
  62. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/hive-conf/hive-site.xml +0 -0
  63. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/query.sql +0 -0
  64. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/catalog/hive.properties +0 -0
  65. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/config.properties +0 -0
  66. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/hadoop-conf/core-site.xml +0 -0
  67. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/hadoop-conf/hive-site.xml +0 -0
  68. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/jvm.config +0 -0
  69. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/log.properties +0 -0
  70. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/presto/worker/etc/node.properties +0 -0
  71. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/.env.example +0 -0
  72. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/garage.toml +0 -0
  73. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/maven/pom.xml +0 -0
  74. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/__init__.py +0 -0
  75. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_aws_cli.py +0 -0
  76. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_multipart.py +0 -0
  77. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_objects.py +0 -0
  78. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_presigned.py +0 -0
  79. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/test_server/tests/test_spark.py +0 -0
  80. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/compose.yml +0 -0
  81. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/conf/hive-site.xml +0 -0
  82. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/hive.properties +0 -0
  83. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/tpcds.properties +0 -0
  84. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/catalog/tpch.properties +0 -0
  85. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/config.properties +0 -0
  86. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/hadoop-conf/core-site.xml +0 -0
  87. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/hadoop-conf/hive-site.xml +0 -0
  88. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/jvm.config +0 -0
  89. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/log.properties +0 -0
  90. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/coordinator/etc/node.properties +0 -0
  91. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/hive.properties +0 -0
  92. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/tpcds.properties +0 -0
  93. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/catalog/tpch.properties +0 -0
  94. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/config.properties +0 -0
  95. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/hadoop-conf/core-site.xml +0 -0
  96. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/hadoop-conf/hive-site.xml +0 -0
  97. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/jvm.config +0 -0
  98. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/log.properties +0 -0
  99. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/etc/node.properties +0 -0
  100. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/hadoop-conf/core-site.xml +0 -0
  101. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/hadoop-conf/hive-site.xml +0 -0
  102. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/lib/postgresql-42.7.4.jar +0 -0
  103. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/README.md +0 -0
  104. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/bucket.png +0 -0
  105. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/login.png +0 -0
  106. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/metastore.png +0 -0
  107. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/minio.png +0 -0
  108. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/runtime.png +0 -0
  109. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/storage.png +0 -0
  110. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/assets/tiny.png +0 -0
  111. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/conf/core-site.xml +0 -0
  112. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/conf/metastore-site.xml +0 -0
  113. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/docker-compose.yml +0 -0
  114. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/minio.properties +0 -0
  115. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/tpcds.properties +0 -0
  116. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/catalog/tpch.properties +0 -0
  117. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/config.properties +0 -0
  118. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/jvm.config +0 -0
  119. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/log.properties +0 -0
  120. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/trino-minio/etc/node.properties +0 -0
  121. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/hive.properties +0 -0
  122. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/tpcds.properties +0 -0
  123. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/catalog/tpch.properties +0 -0
  124. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/config.properties +0 -0
  125. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/hadoop-conf/core-site.xml +0 -0
  126. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/hadoop-conf/hive-site.xml +0 -0
  127. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/jvm.config +0 -0
  128. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/log.properties +0 -0
  129. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/integration/trino/worker/etc/node.properties +0 -0
  130. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/localhost.cnf +0 -0
  131. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/pyproject.toml +0 -0
  132. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/requirements.txt +0 -0
  133. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/hmac_keystore.rs +0 -0
  134. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/credentials/mod.rs +0 -0
  135. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/object_storage_proxy.pyi +0 -0
  136. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/credentials.rs +0 -0
  137. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/keystore.rs +0 -0
  138. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/parsers/mod.rs +0 -0
  139. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/banner.rs +0 -0
  140. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/functions.rs +0 -0
  141. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/mod.rs +0 -0
  142. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/response.rs +0 -0
  143. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/src/utils/validator.rs +0 -0
  144. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/test_integration.sh +0 -0
  145. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/test_server.py +0 -0
  146. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/tests/__init__.py +0 -0
  147. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/tests/test_config.py +0 -0
  148. {object_storage_proxy-0.6.0 → object_storage_proxy-0.6.2}/uv.lock +0 -0
@@ -2,11 +2,8 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches:
6
- - main
7
- - master
8
5
  tags:
9
- - '*'
6
+ - 'v*'
10
7
  pull_request:
11
8
  workflow_dispatch:
12
9
 
@@ -14,3 +14,5 @@ integration/docker/jars/
14
14
  **/__pycache__/
15
15
  **/*.py[cod]
16
16
  target/
17
+ libobject_storage_proxy.so
18
+ .env.minio
@@ -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
 
@@ -1672,7 +1672,7 @@ dependencies = [
1672
1672
 
1673
1673
  [[package]]
1674
1674
  name = "object-storage-proxy"
1675
- version = "0.6.0"
1675
+ version = "0.6.2"
1676
1676
  dependencies = [
1677
1677
  "async-trait",
1678
1678
  "base64",
@@ -1,8 +1,8 @@
1
1
  [package]
2
2
  name = "object-storage-proxy"
3
- version = "0.6.0"
3
+ version = "0.6.2"
4
4
  edition = "2024"
5
- description = "A fast in-process reverse proxy for AWS S3 and IBM COS with a Python interface for auth and credential management."
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 `test:rust` `test:integration` |
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 `target/wheels/` |
25
- | `task wheel:release` | release wheel `target/wheels/` |
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 test down |
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` | run only the Spark tests (`-m spark`) |
33
- | `task integration:test:all` | run full suite including Spark tests |
34
- | `task integration:test:spark` | run only the Spark tests (`-m 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 start test_server.py
135
- task run:release # release build start test_server.py
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 `0.0.0.0:6190`
140
- - HTTPS `0.0.0.0:8443`
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 rust tests integration tests
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 a real [Garage](https://garagehq.deuxfleurs.fr/) S3-compatible storage node inside Docker. All tests live under `integration/test_server/tests/` and use pytest + boto3.
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 bootstrap proxy test teardown
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:garage:bootstrap # create bucket + HMAC key, write .env
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 bootstrap proxy start |
361
- | `integration:down` | Stop proxy stop Garage |
362
- | `integration:run` | Automated: up test → down (teardown on failure too) |
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:server:start` | Start OSP proxy in the background (logs `proxy.log`) |
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` | Run only the Spark tests (`-m spark`) |
375
- | `integration:test:spark:fast` | Same, with `-x` (stop on first failure) |
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/test_spark.py` | Spark s3a read/write: Parquet, JSON, overwrite, empty DataFrame, large DataFrame (10 000 rows) |
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. `server.py` and the pytest fixtures both load this file automatically. The file is gitignored — never commit it.
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.0
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 # 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
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 # just Garage (stays running)
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 # just the OSP proxy (background)
157
- # task integration:server:stop # kill the proxy
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 integration/test_server/proxy.log)"
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 bootstrap proxy start"
293
+ desc: "Full environment: Garage + MinIO up -> bootstrap both -> proxy start"
247
294
  cmds:
248
295
  - task: integration:garage:up
249
- - task: integration:garage:bootstrap
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 stop Garage"
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/ -v -m 'not spark'
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/ -v -x -m 'not spark'
317
+ - uv run pytest tests/ -q -x -m 'not spark and not spark_minio'
269
318
 
270
319
  integration:test:spark:
271
- desc: "Run only the Spark s3a tests"
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 -v -m spark
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/test_spark.py -v -m spark -x
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/ -v
359
+ - uv run pytest tests/ -q
287
360
 
288
361
  integration:run:
289
- desc: "Automated: up test down (tears down even on failure)"
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 = ''
@@ -35,7 +35,7 @@ services:
35
35
  limits:
36
36
  memory: 4G
37
37
  ports:
38
- - "8080:8080" # host 8001 container 8080
38
+ - "8080:8080" # host 8001 -> container 8080
39
39
  # depends_on:
40
40
  # hive-metastore:
41
41
  # condition: service_healthy