Spaces:
Running
Running
File size: 5,905 Bytes
c745a99 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | # 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.
|