Skip to content

Commit 3ad955b

Browse files
authored
Merge pull request #161 from eScienceLab/feature/optional-minio-156
feat: Make MinIO object storage optional (#156)
2 parents 422a4f7 + c7ebfe3 commit 3ad955b

10 files changed

Lines changed: 242 additions & 112 deletions

File tree

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
This project presents a Flask-based API for validating RO-Crates.
44

5+
### Optional MinIO object storage
6+
7+
The RO-Crate Validation Service can validate an RO-Crate's metadata directly from a JSON payload (the `POST v1/ro_crates/validate_metadata` endpoint) without storing anything. This is the default mode.
8+
9+
Optionally, the service can read crates from — and write validation results
10+
back to — a [MinIO](https://min.io/) object store. This is disabled by
11+
default and controlled by the `MINIO_ENABLED` environment variable:
12+
13+
- `MINIO_ENABLED=false` (default): only a stateless validation endpoint is available and nothing is stored.
14+
- `MINIO_ENABLED=true`: the ID endpoints (`POST`/`GET v1/ro_crates/{crate_id}/validation`) are also registered, and a MinIO instance is required. With Docker Compose, start MinIO with its opt-in profile: `docker compose --profile minio up`.
15+
16+
When MinIO is disabled the ID-based endpoints are not registered and return `404`.
17+
518
## API
619

720
#### Request Validation of RO-Crate
@@ -184,8 +197,14 @@ curl -X 'POST' \
184197
```bash
185198
docker compose up --build
186199
```
200+
This runs in the default (metadata-only) mode. To enable the MinIO-backed
201+
endpoints, set `MINIO_ENABLED=true` in your `.env` and start the `minio`
202+
profile:
203+
```bash
204+
docker compose --profile minio up --build
205+
```
187206
188-
5. Set up the MinIO bucket
207+
5. **(Only when `MINIO_ENABLED=true`)** Set up the MinIO bucket
189208
1. Open the MinIO web interface at `http://localhost:9000`.
190209
2. Log in with your MinIO credentials.
191210
3. Create a new bucket named `ro-crates`.

app/__init__.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@
44
# License: MIT
55
# Copyright (c) 2025 eScience Lab, The University of Manchester
66

7+
import logging
78
import os
89

910
from apiflask import APIFlask
1011

11-
from app.ro_crates.routes import v1_post_bp, v1_get_bp
12-
from app.utils.config import DevelopmentConfig, ProductionConfig, InvalidAPIUsage, make_celery
12+
from app.ro_crates.routes import v1_post_bp, v1_minio_post_bp, v1_minio_get_bp
13+
from app.utils.config import (
14+
DevelopmentConfig,
15+
ProductionConfig,
16+
InvalidAPIUsage,
17+
make_celery,
18+
)
1319
from flask import jsonify
1420

21+
logger = logging.getLogger(__name__)
22+
1523

1624
def create_app() -> APIFlask:
1725
"""
@@ -20,23 +28,36 @@ def create_app() -> APIFlask:
2028
:return: Flask: A configured Flask application instance.
2129
"""
2230
app = APIFlask(__name__)
23-
app.register_blueprint(v1_post_bp, url_prefix="/v1/ro_crates")
24-
app.register_blueprint(v1_get_bp, url_prefix="/v1/ro_crates")
2531

26-
@app.errorhandler(InvalidAPIUsage)
27-
def invalid_api_usage(e):
28-
return jsonify(e.to_dict()), e.status_code
29-
30-
# Load configuration:
32+
# Load config before registering blueprints, so MINIO_ENABLED can
33+
# decide whether the backed endpoints are exposed.
3134
if os.getenv("FLASK_ENV") == "production":
3235
app.config.from_object(ProductionConfig)
3336
else:
3437
# Development environment:
3538
app.debug = True
39+
app.config.from_object(DevelopmentConfig)
40+
41+
# Always available:
42+
app.register_blueprint(v1_post_bp, url_prefix="/v1/ro_crates")
43+
44+
# MinIO is optional and disabled by default. Only register
45+
# the MinIO ID routes when enabled:
46+
if app.config.get("MINIO_ENABLED"):
47+
app.register_blueprint(v1_minio_post_bp, url_prefix="/v1/ro_crates")
48+
app.register_blueprint(v1_minio_get_bp, url_prefix="/v1/ro_crates")
49+
logger.info("MinIO storage enabled: ID-based validation endpoints registered.")
50+
else:
51+
logger.info("MinIO storage disabled: only metadata validation is available.")
52+
53+
if app.debug:
3654
print("URL Map:")
3755
for rule in app.url_map.iter_rules():
3856
print(rule)
39-
app.config.from_object(DevelopmentConfig)
57+
58+
@app.errorhandler(InvalidAPIUsage)
59+
def invalid_api_usage(e):
60+
return jsonify(e.to_dict()), e.status_code
4061

4162
# Integrate Celery
4263
make_celery(app)

app/ro_crates/routes/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
# License: MIT
55
# Copyright (c) 2025 eScience Lab, The University of Manchester
66

7-
from app.ro_crates.routes.post_routes import post_routes_bp
7+
from app.ro_crates.routes.post_routes import post_routes_bp, minio_post_routes_bp
88
from app.ro_crates.routes.get_routes import get_routes_bp
99

10+
# Always registered:
1011
v1_post_bp = post_routes_bp
11-
v1_get_bp = get_routes_bp
12+
13+
# Registered only when MinIO is enabled:
14+
v1_minio_post_bp = minio_post_routes_bp
15+
v1_minio_get_bp = get_routes_bp

app/ro_crates/routes/post_routes.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111

1212
from app.services.validation_service import (
1313
queue_ro_crate_validation_task,
14-
queue_ro_crate_metadata_validation_task
14+
queue_ro_crate_metadata_validation_task,
1515
)
1616

17+
# Always-on blueprint:
1718
post_routes_bp = APIBlueprint("post_routes", __name__)
1819

20+
# MinIO blueprint. Only registered when MINIO_ENABLED is true
21+
# (see app.create_app), so the ID-based routes are unreachable by default.
22+
minio_post_routes_bp = APIBlueprint("minio_post_routes", __name__)
23+
1924

2025
class MinioConfig(Schema):
2126
endpoint = String(required=True)
@@ -37,8 +42,8 @@ class ValidateJSON(Schema):
3742
profile_name = String(required=False)
3843

3944

40-
@post_routes_bp.post("<string:crate_id>/validation")
41-
@post_routes_bp.input(ValidateCrate(partial=False), location='json')
45+
@minio_post_routes_bp.post("<string:crate_id>/validation")
46+
@minio_post_routes_bp.input(ValidateCrate(partial=False), location="json")
4247
def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
4348
"""
4449
Endpoint to validate an RO-Crate using its ID from MinIO.
@@ -52,7 +57,7 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
5257
- **accesskey**: Access key / username
5358
- **secret**: Secret / password
5459
- **ssl**: Use SSL encryption? True/False
55-
- **bucket**: The MinIO bucket to access
60+
- **bucket**: The MinIO bucket to access
5661
- **root_path**: The root path containing the RO-Crate. _Optional_
5762
- **profile_name**: The profile name for validation. _Optional_.
5863
- **webhook_url**: The webhook URL where validation results will be sent. _Optional_.
@@ -83,12 +88,13 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
8388

8489
profiles_path = current_app.config["PROFILES_PATH"]
8590

86-
return queue_ro_crate_validation_task(minio_config, crate_id, root_path, profile_name,
87-
webhook_url, profiles_path)
91+
return queue_ro_crate_validation_task(
92+
minio_config, crate_id, root_path, profile_name, webhook_url, profiles_path
93+
)
8894

8995

9096
@post_routes_bp.post("/validate_metadata")
91-
@post_routes_bp.input(ValidateJSON(partial=False), location='json') # -> json_data
97+
@post_routes_bp.input(ValidateJSON(partial=False), location="json") # -> json_data
9298
def validate_ro_crate_metadata(json_data) -> tuple[Response, int]:
9399
"""
94100
Endpoint to validate an RO-Crate JSON file uploaded to the Service.
@@ -113,4 +119,6 @@ def validate_ro_crate_metadata(json_data) -> tuple[Response, int]:
113119

114120
profiles_path = current_app.config["PROFILES_PATH"]
115121

116-
return queue_ro_crate_metadata_validation_task(crate_json, profile_name, profiles_path=profiles_path)
122+
return queue_ro_crate_metadata_validation_task(
123+
crate_json, profile_name, profiles_path=profiles_path
124+
)

app/utils/config.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ def get_env(name: str, default=None, required=False):
1717
return value
1818

1919

20+
def get_bool_env(name: str, default: bool = False) -> bool:
21+
value = get_env(name)
22+
if value is None:
23+
return default
24+
return value.strip().lower() in ("true", "1", "yes", "on")
25+
26+
2027
class Config:
2128
"""Base configuration class for the Flask application."""
2229

@@ -27,14 +34,20 @@ class Config:
2734
# rocrate validator configuration:
2835
PROFILES_PATH = get_env("PROFILES_PATH", required=False)
2936

37+
# Optional MinIO storage. Disabled by default - when False the
38+
# ID validation endpoints are not registered:
39+
MINIO_ENABLED = get_bool_env("MINIO_ENABLED", default=False)
40+
3041

3142
class DevelopmentConfig(Config):
3243
"""Development configuration class."""
44+
3345
DEBUG = True
3446

3547

3648
class ProductionConfig(Config):
3749
"""Production configuration class."""
50+
3851
DEBUG = False
3952

4053

@@ -50,7 +63,7 @@ def __init__(self, message, status_code=None, payload=None):
5063

5164
def to_dict(self):
5265
rv = dict(self.payload or ())
53-
rv['message'] = self.message
66+
rv["message"] = self.message
5467
return rv
5568

5669

docker-compose-develop.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ services:
1212
- FLASK_ENV=development
1313
- CELERY_BROKER_URL=redis://redis:6379/0
1414
- CELERY_RESULT_BACKEND=redis://redis:6379/0
15+
# Optional object storage. Set MINIO_ENABLED=true and start the "minio"
16+
# profile (docker compose --profile minio up) to use
17+
- MINIO_ENABLED=${MINIO_ENABLED:-false}
1518
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
1619
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
1720
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
1821
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME}
1922
- PROFILES_PATH=/app/profiles
2023
depends_on:
2124
- redis
22-
- minio
2325

2426
celery_worker:
2527
build:
@@ -29,9 +31,9 @@ services:
2931
environment:
3032
- CELERY_BROKER_URL=redis://redis:6379/0
3133
- CELERY_RESULT_BACKEND=redis://redis:6379/0
34+
- MINIO_ENABLED=${MINIO_ENABLED:-false}
3235
depends_on:
3336
- redis
34-
- minio
3537
volumes:
3638
- ./tests/data/rocrate_validator_profiles:/app/profiles:ro
3739

@@ -42,6 +44,9 @@ services:
4244

4345
minio:
4446
image: "minio/minio"
47+
# Started with `docker compose --profile minio up`.
48+
profiles:
49+
- minio
4550
ports:
4651
- "9000:9000"
4752
- "9001:9001"

docker-compose.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ services:
1111
- FLASK_ENV=development
1212
- CELERY_BROKER_URL=redis://redis:6379/0
1313
- CELERY_RESULT_BACKEND=redis://redis:6379/0
14+
# Optional object storage. Set MINIO_ENABLED=true and start the "minio"
15+
# profile (docker compose --profile minio up) to use
16+
- MINIO_ENABLED=${MINIO_ENABLED:-false}
1417
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
1518
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
1619
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
1720
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME}
1821
depends_on:
1922
- redis
20-
- minio
2123

2224
celery_worker:
2325
platform: linux/x86_64
@@ -26,9 +28,9 @@ services:
2628
environment:
2729
- CELERY_BROKER_URL=redis://redis:6379/0
2830
- CELERY_RESULT_BACKEND=redis://redis:6379/0
31+
- MINIO_ENABLED=${MINIO_ENABLED:-false}
2932
depends_on:
3033
- redis
31-
- minio
3234

3335
redis:
3436
image: "redis:alpine"
@@ -37,6 +39,9 @@ services:
3739

3840
minio:
3941
image: "minio/minio"
42+
# Started with `docker compose --profile minio up`.
43+
profiles:
44+
- minio
4045
ports:
4146
- "9000:9000"
4247
- "9001:9001"

example.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# MinIO is off by default; only the stateless validation endpoint is exposed when
2+
# disabled. The MINIO_* vars below and the "minio" docker-compose profile are
3+
#only needed when this is true.
4+
MINIO_ENABLED=false
5+
16
MINIO_ROOT_USER=minioadmin
27
MINIO_ROOT_PASSWORD=minioadmin
38
MINIO_BUCKET_NAME=ro-crates

0 commit comments

Comments
 (0)