aws_rl_env / aws_infra /CONTRIBUTING.md
Sizzing's picture
Upload folder using huggingface_hub
c745a99 verified
# Contributing to MiniStack
Thanks for wanting to contribute. The codebase is intentionally simple β€” each AWS service is a single self-contained Python file inside `ministack/services/`. Adding a new service or fixing a bug should take minutes, not hours.
## Project Structure
```
ministack/
β”œβ”€β”€ ministack/
β”‚ β”œβ”€β”€ app.py # ASGI entry point, service routing, reset endpoint
β”‚ β”œβ”€β”€ core/
β”‚ β”‚ β”œβ”€β”€ responses.py # json_response, error_response_json, new_uuid
β”‚ β”‚ β”œβ”€β”€ router.py # detect_service(), SERVICE_PATTERNS
β”‚ β”‚ β”œβ”€β”€ lambda_runtime.py
β”‚ β”‚ └── persistence.py
β”‚ └── services/
β”‚ β”œβ”€β”€ s3.py, sqs.py, sns.py, dynamodb.py, ...
β”‚ └── cognito.py # example of a two-client service file
β”œβ”€β”€ tests/
β”‚ β”œβ”€β”€ conftest.py # pytest fixtures (boto3 clients)
β”‚ └── test_services.py # all integration tests
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ pyproject.toml
└── CHANGELOG.md
```
## Adding a New Service
Every service follows the same 4-step pattern:
### 1. Create `ministack/services/myservice.py`
```python
"""
MyService Emulator.
JSON-based API via X-Amz-Target.
Supports: OperationOne, OperationTwo, ...
"""
import json
import logging
from ministack.core.responses import json_response, error_response_json, new_uuid
logger = logging.getLogger("myservice")
ACCOUNT_ID = "000000000000"
REGION = "us-east-1"
_state: dict = {} # in-memory storage
async def handle_request(method, path, headers, body, query_params):
target = headers.get("x-amz-target", "")
action = target.split(".")[-1] if "." in target else ""
try:
data = json.loads(body) if body else {}
except json.JSONDecodeError:
return error_response_json("SerializationException", "Invalid JSON", 400)
handlers = {
"OperationOne": _operation_one,
"OperationTwo": _operation_two,
}
handler = handlers.get(action)
if not handler:
return error_response_json("InvalidAction", f"Unknown action: {action}", 400)
return handler(data)
def _operation_one(data):
return json_response({"result": "ok"})
def _operation_two(data):
return json_response({})
def reset():
_state.clear()
```
**Protocol guide:**
- JSON services (DynamoDB, SecretsManager, Glue, Athena, Cognito, etc.) β€” use `json_response` / `error_response_json`, route via `X-Amz-Target`
- XML/Query services (S3, SQS, SNS, IAM, STS, RDS, ElastiCache, EC2) β€” build XML responses, route via `Action` query param; use `_xml(status, root_tag, inner)` pattern; verify field names against botocore shapes via `Loader().load_service_model()`
- REST services (Lambda, ECS, Route53) β€” route via URL path
### 2. Register in `ministack/app.py`
```python
from ministack.services import myservice
SERVICE_REGISTRY = {
# ... existing ...
"myservice": {"module": "myservice"},
}
```
If the service needs aliases, add them in the registry entry.
### 3. Add detection to `ministack/core/router.py`
```python
SERVICE_PATTERNS = {
# ... existing ...
"myservice": {
"target_prefixes": ["AWSMyService"], # for X-Amz-Target routing
"host_patterns": [r"myservice\."], # for host-based routing
},
}
```
Add any credential scope or `Action`-based routing as needed.
### 4. Add a fixture to `tests/conftest.py`
```python
@pytest.fixture(scope="session")
def mysvc():
return make_client("myservice")
```
### 5. Add tests to `tests/test_services.py`
```python
def test_myservice_operation_one(mysvc):
resp = mysvc.operation_one(Param="value")
assert resp["result"] == "ok"
```
---
## Running Tests Locally
```bash
# Start the stack
docker compose up -d
# Install test dependencies
pip install boto3 pytest pytest-xdist duckdb docker cbor2
# Parallel-safe phase: run tests that are safe to run concurrently
pytest tests/ -v -n 4 --dist=loadfile -m "not serial"
# Serial/global-state phase: run tests that mutate runtime state or require isolation
pytest tests/ -v -m serial
# Run a specific service
pytest tests/ -v -k "cognito"
```
---
## Code Conventions
- **One file per service** β€” keep everything for a service in `ministack/services/myservice.py`
- **Imports** β€” always `from ministack.core.responses import ...`, never `from core.responses import ...`
- **In-memory state** β€” use module-level dicts (`_things: dict = {}`)
- **reset()** β€” every service must expose a `reset()` that clears all module-level state; it's called by `/_ministack/reset`
- **No external AWS deps** β€” no `boto3`, `botocore`, or `aws-sdk` in service code
- **Minimal dependencies** β€” `duckdb` and `docker` are optional; guard with `try/except ImportError`
- **Error responses** β€” match real AWS error codes and HTTP status codes as closely as possible
- **Logging** β€” `logger = logging.getLogger("servicename")`; DEBUG for request details, INFO for significant events
---
## Pull Request Checklist
- [ ] New service file in `ministack/services/`
- [ ] Registered in `ministack/app.py` SERVICE_REGISTRY
- [ ] Detection patterns added to `ministack/core/router.py`
- [ ] Fixture added to `tests/conftest.py`
- [ ] Tests added and passing (`pytest tests/ -v`)
- [ ] Linting passes (`ruff check ministack/`)
- [ ] Service added to the table in `README.md`
- [ ] Entry added to `CHANGELOG.md`
---
## What We're Looking For
High-value contributions right now:
- **CloudFront** β€” distribution CRUD, invalidations, origin configuration
- **CodeBuild / CodePipeline** β€” CI/CD pipeline stubs
- **AppSync** β€” GraphQL API CRUD
- **SQS FIFO** β€” message group / deduplication support
- **More Cognito flows** β€” hosted UI, federated identity providers, custom auth triggers
---
## Questions?
Open a GitHub Discussion or file an issue with the `question` label.