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.