phlo-rustfs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: phlo-rustfs
3
+ Version: 0.1.0
4
+ Summary: RustFS service plugin for Phlo
5
+ Author-email: Phlo Team <team@phlo.dev>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/plain
9
+ Requires-Dist: phlo>=0.1.0
10
+ Requires-Dist: pyyaml>=6.0.1
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+
15
+ RustFS S3-compatible object storage service plugin for Phlo.
@@ -0,0 +1,62 @@
1
+ # phlo-rustfs
2
+
3
+ RustFS S3-compatible object storage plugin for Phlo.
4
+
5
+ ## Description
6
+
7
+ Provides S3-compatible object storage for the data lake using [RustFS](https://github.com/rustfs/rustfs), an Apache 2.0 licensed, Rust-built, 100% S3-compatible object storage server. Stores Iceberg table data, staging files, and backups.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install phlo-rustfs
13
+ # or
14
+ phlo plugin install rustfs
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Variable | Default | Description |
20
+ | -------------------------------- | ------------- | ---------------------------- |
21
+ | `RUSTFS_ACCESS_KEY` | `rustfsadmin` | Access key (username) |
22
+ | `RUSTFS_SECRET_KEY` | `rustfsadmin` | Secret key (password) |
23
+ | `RUSTFS_API_PORT` | `9000` | S3 API port |
24
+ | `RUSTFS_CONSOLE_PORT` | `9001` | Web console port |
25
+ | `RUSTFS_CORS_ALLOWED_ORIGINS` | `*` | CORS allowed origins (S3) |
26
+ | `RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS` | `*` | CORS allowed origins (console) |
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ phlo services start --service rustfs
32
+ ```
33
+
34
+ This targeted start also prepares `./volumes/rustfs` for the non-root RustFS container and creates
35
+ the default `lake`, `warehouse/`, and `stage/` S3 layout automatically.
36
+
37
+ ## Endpoints
38
+
39
+ - **S3 API**: `http://localhost:9000`
40
+ - **Console**: `http://localhost:9001`
41
+
42
+ ## Migration from MinIO
43
+
44
+ If you're switching from MinIO to RustFS, update your environment variables:
45
+
46
+ ```bash
47
+ # Before (MinIO)
48
+ AWS_S3_ENDPOINT=http://minio:9000
49
+ AWS_ACCESS_KEY_ID=minio
50
+ AWS_SECRET_ACCESS_KEY=minio123
51
+
52
+ # After (RustFS)
53
+ AWS_S3_ENDPOINT=http://rustfs:9000
54
+ AWS_ACCESS_KEY_ID=rustfsadmin
55
+ AWS_SECRET_ACCESS_KEY=rustfsadmin
56
+ ```
57
+
58
+ For data migration, see the [spec documentation](../../docs/architecture/specs/0015-phlo-rustfs.md).
59
+
60
+ ## Entry Points
61
+
62
+ - `phlo.plugins.services` - Provides `RustfsServicePlugin`, `RustfsSetupServicePlugin`
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "phlo-rustfs"
7
+ version = "0.1.0"
8
+ description = "RustFS service plugin for Phlo"
9
+ readme = {text = "RustFS S3-compatible object storage service plugin for Phlo.", content-type = "text/plain"}
10
+ requires-python = ">=3.11"
11
+ authors = [
12
+ {name = "Phlo Team", email = "team@phlo.dev"},
13
+ ]
14
+ license = {text = "MIT"}
15
+ dependencies = [
16
+ "phlo>=0.1.0",
17
+ "pyyaml>=6.0.1",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "pytest>=7.0",
23
+ "ruff>=0.1.0",
24
+ ]
25
+
26
+ [project.entry-points."phlo.plugins.services"]
27
+ rustfs = "phlo_rustfs.plugin:RustfsServicePlugin"
28
+ rustfs-setup = "phlo_rustfs.plugin:RustfsSetupServicePlugin"
29
+
30
+ [project.entry-points."phlo.plugins.resources"]
31
+ rustfs = "phlo_rustfs.plugin:RustfsResourceProvider"
32
+
33
+ [tool.setuptools]
34
+ package-dir = {"" = "src"}
35
+ include-package-data = true
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools.package-data]
41
+ phlo_rustfs = ["service.yaml", "rustfs-setup.yaml", "rustfs-volume-setup.yaml"]
42
+
43
+ [tool.ruff]
44
+ line-length = 100
45
+ target-version = "py311"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """RustFS service plugin package."""
2
+
3
+ from phlo_rustfs.plugin import RustfsServicePlugin
4
+ from phlo_rustfs.settings import RustfsSettings, get_settings
5
+
6
+ __all__ = ["RustfsServicePlugin", "RustfsSettings", "get_settings"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1,136 @@
1
+ """RustFS service plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import resources
6
+ from time import perf_counter
7
+ from typing import Any
8
+
9
+ import yaml
10
+
11
+ from phlo.capabilities import ObjectStoreSpec, ResourceSpec
12
+ from phlo.logging import get_logger
13
+ from phlo.plugins import PluginMetadata, ResourceProviderPlugin, ServicePlugin
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ def _load_service_definition(resource_name: str, service_name: str) -> dict[str, Any]:
19
+ start = perf_counter()
20
+ logger.info(
21
+ "rustfs_service_definition_load_started",
22
+ service_name=service_name,
23
+ resource_name=resource_name,
24
+ )
25
+ service_path = resources.files("phlo_rustfs").joinpath(resource_name)
26
+ try:
27
+ data = yaml.safe_load(service_path.read_text(encoding="utf-8"))
28
+ except Exception:
29
+ logger.error(
30
+ "rustfs_service_definition_load_failed",
31
+ service_name=service_name,
32
+ resource_name=resource_name,
33
+ elapsed_ms=round((perf_counter() - start) * 1000, 2),
34
+ exc_info=True,
35
+ )
36
+ raise
37
+
38
+ service_count = len(data.get("services", {})) if isinstance(data, dict) else None
39
+ logger.info(
40
+ "rustfs_service_definition_load_completed",
41
+ service_name=service_name,
42
+ resource_name=resource_name,
43
+ elapsed_ms=round((perf_counter() - start) * 1000, 2),
44
+ service_count=service_count,
45
+ )
46
+ return data
47
+
48
+
49
+ class RustfsServicePlugin(ServicePlugin):
50
+ """Service plugin for RustFS."""
51
+
52
+ @property
53
+ def metadata(self) -> PluginMetadata:
54
+ """Return metadata describing the RustFS service plugin."""
55
+ return PluginMetadata(
56
+ name="rustfs",
57
+ version="0.1.0",
58
+ description="S3-compatible object storage for data lake (RustFS)",
59
+ author="Phlo Team",
60
+ tags=["core", "storage", "s3"],
61
+ )
62
+
63
+ @property
64
+ def service_definition(self) -> dict[str, Any]:
65
+ """Load the RustFS service definition."""
66
+ return _load_service_definition("service.yaml", "rustfs")
67
+
68
+
69
+ class RustfsSetupServicePlugin(ServicePlugin):
70
+ """Service plugin for RustFS bucket initialization."""
71
+
72
+ @property
73
+ def metadata(self) -> PluginMetadata:
74
+ """Return metadata describing the RustFS setup plugin."""
75
+ return PluginMetadata(
76
+ name="rustfs-setup",
77
+ version="0.1.0",
78
+ description="Initialize RustFS buckets for data lake",
79
+ author="Phlo Team",
80
+ tags=["core", "storage", "bootstrap"],
81
+ )
82
+
83
+ @property
84
+ def service_definition(self) -> dict[str, Any]:
85
+ """Load the RustFS setup service definition."""
86
+ return _load_service_definition("rustfs-setup.yaml", "rustfs-setup")
87
+
88
+
89
+ class RustfsObjectStoreProvider:
90
+ """Capability provider for RustFS-backed object storage."""
91
+
92
+ def to_sling_connection(self) -> dict[str, Any]:
93
+ """Return a Sling-compatible S3 connection definition."""
94
+ from phlo_rustfs.settings import get_settings
95
+
96
+ settings = get_settings()
97
+ return {
98
+ "type": "s3",
99
+ "endpoint": f"http://{settings.rustfs_endpoint()}",
100
+ "access_key_id": settings.rustfs_access_key,
101
+ "secret_access_key": settings.rustfs_secret_key,
102
+ "region": settings.s3_region,
103
+ }
104
+
105
+
106
+ class RustfsResourceProvider(ResourceProviderPlugin):
107
+ """Resource provider plugin for RustFS capabilities."""
108
+
109
+ @property
110
+ def metadata(self) -> PluginMetadata:
111
+ """Return plugin metadata for the RustFS resource provider."""
112
+ return PluginMetadata(
113
+ name="rustfs",
114
+ version="0.1.0",
115
+ description="RustFS object-store capability for Phlo",
116
+ tags=["core", "storage", "s3"],
117
+ )
118
+
119
+ def get_resources(self) -> list[ResourceSpec]:
120
+ """Return resource specs exposed by this provider."""
121
+ return []
122
+
123
+ def get_object_stores(self) -> list[ObjectStoreSpec]:
124
+ """Return object-store capability specs exposed by this provider."""
125
+ provider = RustfsObjectStoreProvider()
126
+ return [
127
+ ObjectStoreSpec(
128
+ name="rustfs",
129
+ provider=provider,
130
+ metadata={
131
+ "storage_system": "s3",
132
+ "type": "s3",
133
+ "endpoint": provider.to_sling_connection()["endpoint"],
134
+ },
135
+ )
136
+ ]
@@ -0,0 +1,28 @@
1
+ # Companion service for rustfs - creates required buckets
2
+ name: rustfs-setup
3
+ description: Initialize RustFS buckets for data lake
4
+ category: core
5
+ default: false
6
+
7
+ image: amazon/aws-cli:2.15.40
8
+
9
+ depends_on:
10
+ - rustfs
11
+
12
+ compose:
13
+ restart: "no"
14
+ entrypoint: /bin/sh
15
+ command: >
16
+ -c "
17
+ until aws --endpoint-url http://rustfs:9000 s3api list-buckets --region us-east-1 >/dev/null 2>&1; do
18
+ echo 'Waiting for RustFS...' && sleep 2;
19
+ done &&
20
+ aws --endpoint-url http://rustfs:9000 s3 mb s3://lake --region us-east-1 || true &&
21
+ aws --endpoint-url http://rustfs:9000 s3api put-object --bucket lake --key warehouse/ --region us-east-1 >/dev/null &&
22
+ aws --endpoint-url http://rustfs:9000 s3api put-object --bucket lake --key stage/ --region us-east-1 >/dev/null &&
23
+ echo 'RustFS bucket prefixes created successfully'
24
+ "
25
+ environment:
26
+ AWS_ACCESS_KEY_ID: ${RUSTFS_ACCESS_KEY:-rustfsadmin}
27
+ AWS_SECRET_ACCESS_KEY: ${RUSTFS_SECRET_KEY:-rustfsadmin}
28
+ AWS_DEFAULT_REGION: us-east-1
@@ -0,0 +1,20 @@
1
+ # Companion service for rustfs - prepares writable bind mount ownership
2
+ name: rustfs-volume-setup
3
+ description: Initialize RustFS data volume permissions
4
+ category: core
5
+ default: false
6
+
7
+ image: alpine:3.20
8
+
9
+ compose:
10
+ restart: "no"
11
+ user: "0:0"
12
+ entrypoint: /bin/sh
13
+ command: >
14
+ -c "
15
+ mkdir -p /data &&
16
+ chown -R 10001:10001 /data &&
17
+ echo 'RustFS data volume ownership initialized'
18
+ "
19
+ volumes:
20
+ - ./volumes/rustfs:/data
@@ -0,0 +1,53 @@
1
+ name: rustfs
2
+ description: S3-compatible object storage for data lake (RustFS)
3
+ category: core
4
+ default: false
5
+
6
+ depends_on:
7
+ - rustfs-volume-setup
8
+
9
+ image: rustfs/rustfs:latest
10
+
11
+ compose:
12
+ restart: unless-stopped
13
+ user: "10001:10001"
14
+ environment:
15
+ RUSTFS_VOLUMES: /data
16
+ RUSTFS_ADDRESS: "0.0.0.0:9000"
17
+ RUSTFS_CONSOLE_ADDRESS: "0.0.0.0:9001"
18
+ RUSTFS_CONSOLE_ENABLE: "true"
19
+ RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY:-rustfsadmin}
20
+ RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY:-rustfsadmin}
21
+ RUSTFS_CORS_ALLOWED_ORIGINS: ${RUSTFS_CORS_ALLOWED_ORIGINS:-*}
22
+ RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS: ${RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS:-*}
23
+ ports:
24
+ - "${RUSTFS_API_PORT:-9000}:9000"
25
+ - "${RUSTFS_CONSOLE_PORT:-9001}:9001"
26
+ volumes:
27
+ - ./volumes/rustfs:/data
28
+ healthcheck:
29
+ test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
30
+ interval: 10s
31
+ timeout: 5s
32
+ retries: 10
33
+
34
+ env_vars:
35
+ RUSTFS_ACCESS_KEY:
36
+ default: rustfsadmin
37
+ description: RustFS access key (username)
38
+ RUSTFS_SECRET_KEY:
39
+ default: rustfsadmin
40
+ description: RustFS secret key (password)
41
+ secret: true
42
+ RUSTFS_API_PORT:
43
+ default: 9000
44
+ description: RustFS S3 API host port
45
+ RUSTFS_CONSOLE_PORT:
46
+ default: 9001
47
+ description: RustFS web console host port
48
+ RUSTFS_CORS_ALLOWED_ORIGINS:
49
+ default: "*"
50
+ description: CORS allowed origins for S3 API
51
+ RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS:
52
+ default: "*"
53
+ description: CORS allowed origins for web console
@@ -0,0 +1,30 @@
1
+ """RustFS settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import lru_cache
6
+
7
+ from pydantic import Field
8
+
9
+ from phlo.config.base import BaseConfig
10
+
11
+
12
+ class RustfsSettings(BaseConfig):
13
+ """RustFS S3-compatible storage configuration."""
14
+
15
+ rustfs_host: str = Field(default="rustfs", description="RustFS service hostname")
16
+ rustfs_access_key: str = Field(default="rustfsadmin", description="RustFS access key")
17
+ rustfs_secret_key: str = Field(default="rustfsadmin", description="RustFS secret key")
18
+ rustfs_api_port: int = Field(default=9000, description="RustFS S3 API port")
19
+ rustfs_console_port: int = Field(default=9001, description="RustFS console port")
20
+ s3_region: str = Field(default="us-east-1", description="S3 region")
21
+
22
+ def rustfs_endpoint(self) -> str:
23
+ """Return host:port endpoint for RustFS S3 API."""
24
+ return f"{self.rustfs_host}:{self.rustfs_api_port}"
25
+
26
+
27
+ @lru_cache(maxsize=1)
28
+ def get_settings() -> RustfsSettings:
29
+ """Return cached RustFS settings."""
30
+ return RustfsSettings()
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: phlo-rustfs
3
+ Version: 0.1.0
4
+ Summary: RustFS service plugin for Phlo
5
+ Author-email: Phlo Team <team@phlo.dev>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/plain
9
+ Requires-Dist: phlo>=0.1.0
10
+ Requires-Dist: pyyaml>=6.0.1
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+
15
+ RustFS S3-compatible object storage service plugin for Phlo.
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/phlo_rustfs/__init__.py
4
+ src/phlo_rustfs/plugin.py
5
+ src/phlo_rustfs/rustfs-setup.yaml
6
+ src/phlo_rustfs/rustfs-volume-setup.yaml
7
+ src/phlo_rustfs/service.yaml
8
+ src/phlo_rustfs/settings.py
9
+ src/phlo_rustfs.egg-info/PKG-INFO
10
+ src/phlo_rustfs.egg-info/SOURCES.txt
11
+ src/phlo_rustfs.egg-info/dependency_links.txt
12
+ src/phlo_rustfs.egg-info/entry_points.txt
13
+ src/phlo_rustfs.egg-info/requires.txt
14
+ src/phlo_rustfs.egg-info/top_level.txt
15
+ tests/test_rustfs_plugin.py
16
+ tests/test_rustfs_settings.py
@@ -0,0 +1,6 @@
1
+ [phlo.plugins.resources]
2
+ rustfs = phlo_rustfs.plugin:RustfsResourceProvider
3
+
4
+ [phlo.plugins.services]
5
+ rustfs = phlo_rustfs.plugin:RustfsServicePlugin
6
+ rustfs-setup = phlo_rustfs.plugin:RustfsSetupServicePlugin
@@ -0,0 +1,6 @@
1
+ phlo>=0.1.0
2
+ pyyaml>=6.0.1
3
+
4
+ [dev]
5
+ pytest>=7.0
6
+ ruff>=0.1.0
@@ -0,0 +1 @@
1
+ phlo_rustfs
@@ -0,0 +1,67 @@
1
+ """Tests for RustFS service plugin."""
2
+
3
+ from phlo_rustfs.plugin import RustfsResourceProvider, RustfsServicePlugin, RustfsSetupServicePlugin
4
+
5
+
6
+ def test_rustfs_service_definition():
7
+ """Validate RustFS service definition fields."""
8
+ plugin = RustfsServicePlugin()
9
+ service_definition = plugin.service_definition
10
+
11
+ assert service_definition["name"] == "rustfs"
12
+ assert service_definition["category"] == "core"
13
+ assert service_definition["default"] is False
14
+ assert "rustfs/rustfs" in service_definition["image"]
15
+ assert "rustfs-volume-setup" in service_definition["depends_on"]
16
+
17
+
18
+ def test_rustfs_plugin_metadata():
19
+ """Validate RustFS plugin metadata."""
20
+ plugin = RustfsServicePlugin()
21
+ metadata = plugin.metadata
22
+
23
+ assert metadata.name == "rustfs"
24
+ assert metadata.version == "0.1.0"
25
+ assert "storage" in metadata.tags
26
+ assert "s3" in metadata.tags
27
+
28
+
29
+ def test_rustfs_setup_service_definition():
30
+ """Validate RustFS setup service definition fields."""
31
+ plugin = RustfsSetupServicePlugin()
32
+ service_definition = plugin.service_definition
33
+
34
+ assert service_definition["name"] == "rustfs-setup"
35
+ assert service_definition["category"] == "core"
36
+ assert "rustfs" in service_definition["depends_on"]
37
+
38
+
39
+ def test_rustfs_setup_plugin_metadata():
40
+ """Validate RustFS setup plugin metadata."""
41
+ plugin = RustfsSetupServicePlugin()
42
+ metadata = plugin.metadata
43
+
44
+ assert metadata.name == "rustfs-setup"
45
+ assert "bootstrap" in metadata.tags
46
+
47
+
48
+ def test_rustfs_resource_provider_exposes_object_store(monkeypatch) -> None:
49
+ """RustFS should expose an object_store capability."""
50
+ monkeypatch.setattr(
51
+ "phlo_rustfs.plugin.RustfsObjectStoreProvider.to_sling_connection",
52
+ lambda _self: {
53
+ "type": "s3",
54
+ "endpoint": "http://rustfs:9000",
55
+ "access_key_id": "rustfs",
56
+ "secret_access_key": "secret",
57
+ "region": "us-east-1",
58
+ },
59
+ )
60
+
61
+ provider = RustfsResourceProvider()
62
+
63
+ object_stores = provider.get_object_stores()
64
+
65
+ assert len(object_stores) == 1
66
+ assert object_stores[0].name == "rustfs"
67
+ assert object_stores[0].metadata["endpoint"] == "http://rustfs:9000"
@@ -0,0 +1,39 @@
1
+ """Tests for RustFS settings."""
2
+
3
+ from phlo_rustfs.settings import RustfsSettings
4
+
5
+
6
+ def test_rustfs_settings_defaults():
7
+ """Validate default settings values."""
8
+ settings = RustfsSettings()
9
+
10
+ assert settings.rustfs_host == "rustfs"
11
+ assert settings.rustfs_access_key == "rustfsadmin"
12
+ assert settings.rustfs_secret_key == "rustfsadmin"
13
+ assert settings.rustfs_api_port == 9000
14
+ assert settings.rustfs_console_port == 9001
15
+ assert settings.s3_region == "us-east-1"
16
+
17
+
18
+ def test_rustfs_endpoint():
19
+ """Validate endpoint format."""
20
+ settings = RustfsSettings()
21
+ endpoint = settings.rustfs_endpoint()
22
+
23
+ assert endpoint == "rustfs:9000"
24
+
25
+
26
+ def test_rustfs_endpoint_custom_port():
27
+ """Validate endpoint with custom port."""
28
+ settings = RustfsSettings(rustfs_api_port=19000)
29
+ endpoint = settings.rustfs_endpoint()
30
+
31
+ assert endpoint == "rustfs:19000"
32
+
33
+
34
+ def test_rustfs_endpoint_custom_host():
35
+ """Validate endpoint with custom host."""
36
+ settings = RustfsSettings(rustfs_host="storage.local")
37
+ endpoint = settings.rustfs_endpoint()
38
+
39
+ assert endpoint == "storage.local:9000"