stapel-tools 0.3.1__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.
- stapel_tools-0.3.1/PKG-INFO +112 -0
- stapel_tools-0.3.1/README.md +105 -0
- stapel_tools-0.3.1/pyproject.toml +31 -0
- stapel_tools-0.3.1/setup.cfg +4 -0
- stapel_tools-0.3.1/stapel_tools/__init__.py +1 -0
- stapel_tools-0.3.1/stapel_tools/_compose_templates.py +373 -0
- stapel_tools-0.3.1/stapel_tools/_library_templates.py +833 -0
- stapel_tools-0.3.1/stapel_tools/_minimal_templates.py +144 -0
- stapel_tools-0.3.1/stapel_tools/_templates.py +474 -0
- stapel_tools-0.3.1/stapel_tools/create_project.py +718 -0
- stapel_tools-0.3.1/stapel_tools/lint.py +412 -0
- stapel_tools-0.3.1/stapel_tools/new_library.py +205 -0
- stapel_tools-0.3.1/stapel_tools/new_module.py +123 -0
- stapel_tools-0.3.1/stapel_tools/new_service.py +607 -0
- stapel_tools-0.3.1/stapel_tools/remove_service.py +297 -0
- stapel_tools-0.3.1/stapel_tools.egg-info/PKG-INFO +112 -0
- stapel_tools-0.3.1/stapel_tools.egg-info/SOURCES.txt +20 -0
- stapel_tools-0.3.1/stapel_tools.egg-info/dependency_links.txt +1 -0
- stapel_tools-0.3.1/stapel_tools.egg-info/entry_points.txt +7 -0
- stapel_tools-0.3.1/stapel_tools.egg-info/top_level.txt +1 -0
- stapel_tools-0.3.1/tests/test_create_project.py +148 -0
- stapel_tools-0.3.1/tests/test_new_library.py +168 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stapel-tools
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: CLI scaffold and linting tools for Stapel/Django microservices
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# stapel-tools
|
|
9
|
+
|
|
10
|
+
CLI scaffold and linting tools for Stapel/Django projects.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install git+https://github.com/usestapel/stapel-tools.git
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or as a dev dependency in your project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e path/to/stapel-tools
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
### `stapel-create-project` — interactive project wizard
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
stapel-create-project # full wizard
|
|
30
|
+
stapel-create-project my-app --type monolith # skip some wizard steps
|
|
31
|
+
stapel-create-project my-app \
|
|
32
|
+
--type monolith \
|
|
33
|
+
--title "My App" \
|
|
34
|
+
--url https://myapp.com \
|
|
35
|
+
--company-name "ACME" \
|
|
36
|
+
--company-email hello@myapp.com \
|
|
37
|
+
--modules auth billing # fully non-interactive
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Project types: `monolith` (recommended), `microservices`, `minimal` (no Docker, SQLite).
|
|
41
|
+
|
|
42
|
+
### `stapel-new-service` — add a service to an existing project
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
stapel-new-service auth
|
|
46
|
+
stapel-new-service auth --title "Auth Service" --prefix iron-
|
|
47
|
+
stapel-new-service blog --celery
|
|
48
|
+
stapel-new-service blog --dry-run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `stapel-new-library` — scaffold a standalone stapel-* package repo
|
|
52
|
+
|
|
53
|
+
For contributing a new reusable package to the framework (or building your
|
|
54
|
+
own to the same standard). Materializes the Stapel library standard: flat
|
|
55
|
+
layout, `STAPEL_<NAME>` settings namespace, comm surface with JSON schemas,
|
|
56
|
+
serializer seams, MODULE.md, community files, CI with the codecov
|
|
57
|
+
ratchet/floor policy, ruff git hooks. The generated repo's own test suite
|
|
58
|
+
is green out of the box.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
stapel-new-library search # L2 service module
|
|
62
|
+
stapel-new-library attributes --kind library # L1 importable lib
|
|
63
|
+
stapel-new-library support-chat --title "Support chat" --dir ~/Projects
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Kinds: `module` (default — Django app with models/views/comm surface;
|
|
67
|
+
modules never import each other) and `library` (importable package without
|
|
68
|
+
service identity, like stapel-attributes).
|
|
69
|
+
|
|
70
|
+
### `stapel-new-module` — add a Django app to a service
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd svc-auth/
|
|
74
|
+
stapel-new-module users
|
|
75
|
+
stapel-new-module billing --title "Billing Plans"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `stapel-remove-service` — remove a service
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
stapel-remove-service auth
|
|
82
|
+
stapel-remove-service auth --prefix iron- --yes
|
|
83
|
+
stapel-remove-service auth --dry-run
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `stapel-lint` — project-specific static linter
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
stapel-lint # scan current directory
|
|
90
|
+
stapel-lint svc-auth/ # scan specific service
|
|
91
|
+
stapel-lint --stats # show per-rule counts
|
|
92
|
+
stapel-lint --ignore R002 # skip a rule
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Rules: R001 bare `Response()`, R002 `serializers.ValidationError`, R003 undocumented `@action`,
|
|
96
|
+
R004 `@dataclass` without docstring, R005 hardcoded error string, R006 `StapelResponse(dict)`.
|
|
97
|
+
|
|
98
|
+
Suppress per-line: `# noqa: R001`
|
|
99
|
+
|
|
100
|
+
## Available modules
|
|
101
|
+
|
|
102
|
+
| Module | Description |
|
|
103
|
+
|--------|-------------|
|
|
104
|
+
| `core` | Core framework (always included) |
|
|
105
|
+
| `auth` | Authentication — JWT, OAuth, OTP |
|
|
106
|
+
| `billing` | Billing & subscriptions |
|
|
107
|
+
| `cdn` | File uploads & CDN |
|
|
108
|
+
| `notifications` | Email / push notifications |
|
|
109
|
+
| `profiles` | User profiles |
|
|
110
|
+
| `translate` | Translations & i18n |
|
|
111
|
+
| `workspaces` | Workspaces & multi-tenancy |
|
|
112
|
+
| `gdpr` | GDPR — data export & deletion |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# stapel-tools
|
|
2
|
+
|
|
3
|
+
CLI scaffold and linting tools for Stapel/Django projects.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install git+https://github.com/usestapel/stapel-tools.git
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or as a dev dependency in your project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -e path/to/stapel-tools
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### `stapel-create-project` — interactive project wizard
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
stapel-create-project # full wizard
|
|
23
|
+
stapel-create-project my-app --type monolith # skip some wizard steps
|
|
24
|
+
stapel-create-project my-app \
|
|
25
|
+
--type monolith \
|
|
26
|
+
--title "My App" \
|
|
27
|
+
--url https://myapp.com \
|
|
28
|
+
--company-name "ACME" \
|
|
29
|
+
--company-email hello@myapp.com \
|
|
30
|
+
--modules auth billing # fully non-interactive
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Project types: `monolith` (recommended), `microservices`, `minimal` (no Docker, SQLite).
|
|
34
|
+
|
|
35
|
+
### `stapel-new-service` — add a service to an existing project
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
stapel-new-service auth
|
|
39
|
+
stapel-new-service auth --title "Auth Service" --prefix iron-
|
|
40
|
+
stapel-new-service blog --celery
|
|
41
|
+
stapel-new-service blog --dry-run
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `stapel-new-library` — scaffold a standalone stapel-* package repo
|
|
45
|
+
|
|
46
|
+
For contributing a new reusable package to the framework (or building your
|
|
47
|
+
own to the same standard). Materializes the Stapel library standard: flat
|
|
48
|
+
layout, `STAPEL_<NAME>` settings namespace, comm surface with JSON schemas,
|
|
49
|
+
serializer seams, MODULE.md, community files, CI with the codecov
|
|
50
|
+
ratchet/floor policy, ruff git hooks. The generated repo's own test suite
|
|
51
|
+
is green out of the box.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
stapel-new-library search # L2 service module
|
|
55
|
+
stapel-new-library attributes --kind library # L1 importable lib
|
|
56
|
+
stapel-new-library support-chat --title "Support chat" --dir ~/Projects
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Kinds: `module` (default — Django app with models/views/comm surface;
|
|
60
|
+
modules never import each other) and `library` (importable package without
|
|
61
|
+
service identity, like stapel-attributes).
|
|
62
|
+
|
|
63
|
+
### `stapel-new-module` — add a Django app to a service
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cd svc-auth/
|
|
67
|
+
stapel-new-module users
|
|
68
|
+
stapel-new-module billing --title "Billing Plans"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `stapel-remove-service` — remove a service
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
stapel-remove-service auth
|
|
75
|
+
stapel-remove-service auth --prefix iron- --yes
|
|
76
|
+
stapel-remove-service auth --dry-run
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `stapel-lint` — project-specific static linter
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
stapel-lint # scan current directory
|
|
83
|
+
stapel-lint svc-auth/ # scan specific service
|
|
84
|
+
stapel-lint --stats # show per-rule counts
|
|
85
|
+
stapel-lint --ignore R002 # skip a rule
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Rules: R001 bare `Response()`, R002 `serializers.ValidationError`, R003 undocumented `@action`,
|
|
89
|
+
R004 `@dataclass` without docstring, R005 hardcoded error string, R006 `StapelResponse(dict)`.
|
|
90
|
+
|
|
91
|
+
Suppress per-line: `# noqa: R001`
|
|
92
|
+
|
|
93
|
+
## Available modules
|
|
94
|
+
|
|
95
|
+
| Module | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `core` | Core framework (always included) |
|
|
98
|
+
| `auth` | Authentication — JWT, OAuth, OTP |
|
|
99
|
+
| `billing` | Billing & subscriptions |
|
|
100
|
+
| `cdn` | File uploads & CDN |
|
|
101
|
+
| `notifications` | Email / push notifications |
|
|
102
|
+
| `profiles` | User profiles |
|
|
103
|
+
| `translate` | Translations & i18n |
|
|
104
|
+
| `workspaces` | Workspaces & multi-tenancy |
|
|
105
|
+
| `gdpr` | GDPR — data export & deletion |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stapel-tools"
|
|
7
|
+
version = "0.3.1"
|
|
8
|
+
description = "CLI scaffold and linting tools for Stapel/Django microservices"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
stapel-lint = "stapel_tools.lint:main"
|
|
15
|
+
stapel-new-service = "stapel_tools.new_service:main"
|
|
16
|
+
stapel-new-module = "stapel_tools.new_module:main"
|
|
17
|
+
stapel-new-library = "stapel_tools.new_library:main"
|
|
18
|
+
stapel-remove-service = "stapel_tools.remove_service:main"
|
|
19
|
+
stapel-create-project = "stapel_tools.create_project:main"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["."]
|
|
23
|
+
include = ["stapel_tools*"]
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 100
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
|
|
29
|
+
[tool.ruff.lint]
|
|
30
|
+
select = ["E", "F", "W", "I"]
|
|
31
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Docker Compose templates for project scaffolding."""
|
|
2
|
+
|
|
3
|
+
# Runs on EVERY postgres startup (via the db service's command wrapper),
|
|
4
|
+
# creating any database listed in POSTGRES_MULTIPLE_DATABASES that does not
|
|
5
|
+
# exist yet. Running at every startup — not just first initdb — means adding
|
|
6
|
+
# a service later creates its database without wiping the data volume.
|
|
7
|
+
POSTGRES_ENSURE_DATABASES = """\
|
|
8
|
+
#!/bin/sh
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
ensure_database_exists() {
|
|
12
|
+
database=$1
|
|
13
|
+
if psql -v ON_ERROR_STOP=1 -d postgres --username "$POSTGRES_USER" -lqt \\
|
|
14
|
+
| cut -d '|' -f 1 | grep -qw "$database"; then
|
|
15
|
+
echo " database '$database' exists"
|
|
16
|
+
else
|
|
17
|
+
echo " creating database '$database'"
|
|
18
|
+
psql -v ON_ERROR_STOP=1 -d postgres --username "$POSTGRES_USER" <<-EOSQL
|
|
19
|
+
CREATE DATABASE $database;
|
|
20
|
+
GRANT ALL PRIVILEGES ON DATABASE $database TO $POSTGRES_USER;
|
|
21
|
+
EOSQL
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if [ -n "${POSTGRES_MULTIPLE_DATABASES:-}" ]; then
|
|
26
|
+
echo "Ensuring databases exist: $POSTGRES_MULTIPLE_DATABASES"
|
|
27
|
+
for db in $(echo "$POSTGRES_MULTIPLE_DATABASES" | tr ',' ' '); do
|
|
28
|
+
db=$(echo "$db" | xargs)
|
|
29
|
+
[ -n "$db" ] && ensure_database_exists "$db"
|
|
30
|
+
done
|
|
31
|
+
fi
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Mounted at /etc/nginx/conf.d/nginx.conf. stapel-new-service appends a
|
|
35
|
+
# location block per service before the closing brace.
|
|
36
|
+
NGINX_CONF = """\
|
|
37
|
+
server {
|
|
38
|
+
listen 80;
|
|
39
|
+
server_name _;
|
|
40
|
+
client_max_body_size 50m;
|
|
41
|
+
resolver 127.0.0.11 valid=10s;
|
|
42
|
+
|
|
43
|
+
location /staticfiles/ {
|
|
44
|
+
alias /staticfiles/;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
location /media/ {
|
|
48
|
+
alias /media/;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ─── Broker building blocks ─────────────────────────────────────────────────
|
|
55
|
+
# Compose bases carry {{BROKER_SERVICES}} / {{BROKER_VOLUMES}} markers; the
|
|
56
|
+
# generator splices the chosen broker(s) in. Env templates carry
|
|
57
|
+
# {{BROKER_ENV}}. A dedicated Task broker (--task-broker) adds its blocks
|
|
58
|
+
# next to the event broker's.
|
|
59
|
+
|
|
60
|
+
NATS_SERVICE_BLOCK = """\
|
|
61
|
+
# Events (JetStream) + RPC (request-reply) for stapel_core.comm
|
|
62
|
+
nats:
|
|
63
|
+
image: nats:2.10-alpine
|
|
64
|
+
restart: unless-stopped
|
|
65
|
+
command: ["--jetstream", "--store_dir", "/data"]
|
|
66
|
+
volumes:
|
|
67
|
+
- nats-data:/data
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
KAFKA_SERVICE_BLOCK = """\
|
|
72
|
+
kafka:
|
|
73
|
+
image: apache/kafka:3.9.0
|
|
74
|
+
restart: unless-stopped
|
|
75
|
+
environment:
|
|
76
|
+
KAFKA_NODE_ID: 1
|
|
77
|
+
KAFKA_PROCESS_ROLES: broker,controller
|
|
78
|
+
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
|
|
79
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
|
80
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
|
|
81
|
+
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
82
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
83
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
|
|
84
|
+
volumes:
|
|
85
|
+
- kafka-data:/var/lib/kafka/data
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
NATS_ENV_BLOCK = """\
|
|
90
|
+
# ─── NATS: events (JetStream) + RPC ─────────────────────────────────────────
|
|
91
|
+
STAPEL_BUS_BACKEND=nats
|
|
92
|
+
NATS_URL=nats://nats:4222
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
KAFKA_ENV_BLOCK = """\
|
|
96
|
+
# ─── Kafka: events ──────────────────────────────────────────────────────────
|
|
97
|
+
STAPEL_BUS_BACKEND=kafka
|
|
98
|
+
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# Task broker only (monolith --task-broker nats): Actions stay in-process;
|
|
102
|
+
# STAPEL_TASK_DISPATCH=bus makes stapel_core publish task.* events through
|
|
103
|
+
# the broker to a dedicated worker (STAPEL_COMM["TASK_DISPATCH"]).
|
|
104
|
+
TASK_ONLY_NATS_ENV_BLOCK = """\
|
|
105
|
+
# ─── NATS: broker for long-running Tasks only (Actions stay in-process) ────
|
|
106
|
+
STAPEL_BUS_BACKEND=nats
|
|
107
|
+
NATS_URL=nats://nats:4222
|
|
108
|
+
STAPEL_TASK_DISPATCH=bus
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
_BROKER_SERVICES = {"nats": NATS_SERVICE_BLOCK, "kafka": KAFKA_SERVICE_BLOCK, "none": ""}
|
|
112
|
+
_BROKER_VOLUMES = {"nats": " nats-data:\n", "kafka": " kafka-data:\n", "none": ""}
|
|
113
|
+
_BROKER_ENV = {"nats": NATS_ENV_BLOCK, "kafka": KAFKA_ENV_BLOCK, "none": ""}
|
|
114
|
+
_BROKER_URL_LINES = {
|
|
115
|
+
"nats": "NATS_URL=nats://nats:4222\n",
|
|
116
|
+
"kafka": "KAFKA_BOOTSTRAP_SERVERS=kafka:9092\n",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def render_compose_base(template: str, broker: str, task_broker: str = "none") -> str:
|
|
121
|
+
"""Splice the chosen broker(s) (nats | kafka | none) into a compose base.
|
|
122
|
+
|
|
123
|
+
*task_broker* adds a second broker dedicated to Tasks when it differs
|
|
124
|
+
from the event broker.
|
|
125
|
+
"""
|
|
126
|
+
brokers = [b for b in ("nats", "kafka") if b in (broker, task_broker)]
|
|
127
|
+
services = "".join(_BROKER_SERVICES[b] for b in brokers)
|
|
128
|
+
volumes = "".join(_BROKER_VOLUMES[b] for b in brokers)
|
|
129
|
+
return template.replace("{{BROKER_SERVICES}}", services).replace(
|
|
130
|
+
"{{BROKER_VOLUMES}}", volumes
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _broker_env(broker: str, task_broker: str) -> str:
|
|
135
|
+
if task_broker in ("none", broker):
|
|
136
|
+
return _BROKER_ENV[broker]
|
|
137
|
+
if broker == "none":
|
|
138
|
+
# Broker exists for Tasks only — Actions stay in-process.
|
|
139
|
+
return TASK_ONLY_NATS_ENV_BLOCK
|
|
140
|
+
# Two brokers: RoutingBus splits by topic prefix — task.* to the task
|
|
141
|
+
# broker, everything else to the event broker.
|
|
142
|
+
urls = "".join(
|
|
143
|
+
_BROKER_URL_LINES[b] for b in ("nats", "kafka") if b in (broker, task_broker)
|
|
144
|
+
)
|
|
145
|
+
return (
|
|
146
|
+
f"# ─── Brokers: {broker} for events/RPC, {task_broker} for Tasks ─────────────────\n"
|
|
147
|
+
"STAPEL_BUS_BACKEND=routing\n"
|
|
148
|
+
f'STAPEL_BUS_ROUTES={{"task.": "{task_broker}", "": "{broker}"}}\n'
|
|
149
|
+
f"{urls}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_env(template: str, broker: str, ctx: dict, task_broker: str = "none") -> str:
|
|
154
|
+
# Format first ({{BROKER_ENV}} collapses to {BROKER_ENV}), then splice the
|
|
155
|
+
# broker block in — it may contain literal braces (STAPEL_BUS_ROUTES JSON)
|
|
156
|
+
# that str.format must never see.
|
|
157
|
+
rendered = template.format(**ctx)
|
|
158
|
+
return rendered.replace("{BROKER_ENV}", _broker_env(broker, task_broker))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
MONOLITH_COMPOSE_BASE = """\
|
|
162
|
+
# Shared infrastructure — included by all environments.
|
|
163
|
+
# Do not put service-specific overrides here.
|
|
164
|
+
services:
|
|
165
|
+
db:
|
|
166
|
+
image: postgres:16
|
|
167
|
+
restart: unless-stopped
|
|
168
|
+
environment:
|
|
169
|
+
POSTGRES_USER: "${POSTGRES_USER:-stapel}"
|
|
170
|
+
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
|
171
|
+
POSTGRES_MULTIPLE_DATABASES: "stapel_main"
|
|
172
|
+
volumes:
|
|
173
|
+
- db-data:/var/lib/postgresql/data
|
|
174
|
+
- ./service-configs/postgres/ensure-databases.sh:/usr/local/bin/ensure-databases.sh:ro
|
|
175
|
+
command: >
|
|
176
|
+
bash -c "
|
|
177
|
+
docker-entrypoint.sh postgres &
|
|
178
|
+
until pg_isready -U $${POSTGRES_USER:-stapel}; do sleep 1; done
|
|
179
|
+
/usr/local/bin/ensure-databases.sh
|
|
180
|
+
wait
|
|
181
|
+
"
|
|
182
|
+
healthcheck:
|
|
183
|
+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-stapel}"]
|
|
184
|
+
interval: 5s
|
|
185
|
+
timeout: 5s
|
|
186
|
+
retries: 10
|
|
187
|
+
|
|
188
|
+
redis:
|
|
189
|
+
image: redis:7-alpine
|
|
190
|
+
restart: unless-stopped
|
|
191
|
+
volumes:
|
|
192
|
+
- redis-data:/data
|
|
193
|
+
|
|
194
|
+
{{BROKER_SERVICES}} nginx:
|
|
195
|
+
image: nginx:alpine
|
|
196
|
+
restart: unless-stopped
|
|
197
|
+
ports:
|
|
198
|
+
- "${HTTP_PORT:-80}:80"
|
|
199
|
+
volumes:
|
|
200
|
+
- ./service-configs/nginx:/etc/nginx/conf.d:ro
|
|
201
|
+
- static-content:/staticfiles:ro
|
|
202
|
+
- media-content:/media:ro
|
|
203
|
+
depends_on: []
|
|
204
|
+
|
|
205
|
+
volumes:
|
|
206
|
+
db-data:
|
|
207
|
+
redis-data:
|
|
208
|
+
{{BROKER_VOLUMES}} static-content:
|
|
209
|
+
media-content:
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
MONOLITH_COMPOSE_LOCAL = """\
|
|
213
|
+
include:
|
|
214
|
+
- docker-compose.base.yml
|
|
215
|
+
|
|
216
|
+
services:
|
|
217
|
+
# Add services from their individual .yml files:
|
|
218
|
+
# svc-app:
|
|
219
|
+
# extends:
|
|
220
|
+
# file: svc-app.yml
|
|
221
|
+
# service: svc-app
|
|
222
|
+
# volumes:
|
|
223
|
+
# - ./svc-app:/app
|
|
224
|
+
# - ./stapel_core:/app/stapel_core:ro
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
MONOLITH_COMPOSE_DEV = """\
|
|
228
|
+
include:
|
|
229
|
+
- docker-compose.base.yml
|
|
230
|
+
|
|
231
|
+
services:
|
|
232
|
+
# Add services from their individual .yml files:
|
|
233
|
+
# svc-app:
|
|
234
|
+
# extends:
|
|
235
|
+
# file: svc-app.yml
|
|
236
|
+
# service: svc-app
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
MONOLITH_ENV_TEMPLATE = """\
|
|
240
|
+
# ─── Database ──────────────────────────────────────────────────────────────
|
|
241
|
+
POSTGRES_USER=stapel
|
|
242
|
+
POSTGRES_PASSWORD=change_me
|
|
243
|
+
POSTGRES_HOST=db
|
|
244
|
+
POSTGRES_PORT=5432
|
|
245
|
+
|
|
246
|
+
# ─── Redis ─────────────────────────────────────────────────────────────────
|
|
247
|
+
REDIS_URL=redis://redis:6379/0
|
|
248
|
+
|
|
249
|
+
{{BROKER_ENV}}
|
|
250
|
+
# ─── App ───────────────────────────────────────────────────────────────────
|
|
251
|
+
SECRET_KEY=change_me_to_a_long_random_string
|
|
252
|
+
JWT_SECRET_KEY=change_me_to_another_long_random_string
|
|
253
|
+
ALLOWED_HOSTS={domain}
|
|
254
|
+
SITE_URL={url}
|
|
255
|
+
|
|
256
|
+
# ─── Email ─────────────────────────────────────────────────────────────────
|
|
257
|
+
DEFAULT_FROM_EMAIL={company_name} <{company_email}>
|
|
258
|
+
EMAIL_HOST=smtp.example.com
|
|
259
|
+
EMAIL_PORT=587
|
|
260
|
+
EMAIL_HOST_USER=
|
|
261
|
+
EMAIL_HOST_PASSWORD=
|
|
262
|
+
|
|
263
|
+
# ─── OAuth (optional) ───────────────────────────────────────────────────────
|
|
264
|
+
GOOGLE_OAUTH2_KEY=
|
|
265
|
+
GOOGLE_OAUTH2_SECRET=
|
|
266
|
+
|
|
267
|
+
# ─── Run command ────────────────────────────────────────────────────────────
|
|
268
|
+
RUN_CMD=gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 2
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
MONOLITH_GITIGNORE = """\
|
|
272
|
+
.env
|
|
273
|
+
*.pyc
|
|
274
|
+
__pycache__/
|
|
275
|
+
.venv/
|
|
276
|
+
venv/
|
|
277
|
+
*.egg-info/
|
|
278
|
+
dist/
|
|
279
|
+
build/
|
|
280
|
+
htmlcov/
|
|
281
|
+
.coverage
|
|
282
|
+
*.sqlite3
|
|
283
|
+
media/
|
|
284
|
+
staticfiles/
|
|
285
|
+
.DS_Store
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
MICRO_COMPOSE_BASE = """\
|
|
289
|
+
# Shared infrastructure for microservices stack.
|
|
290
|
+
services:
|
|
291
|
+
db:
|
|
292
|
+
image: postgres:16
|
|
293
|
+
restart: unless-stopped
|
|
294
|
+
environment:
|
|
295
|
+
POSTGRES_USER: "${POSTGRES_USER:-stapel}"
|
|
296
|
+
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
|
297
|
+
POSTGRES_MULTIPLE_DATABASES: ""
|
|
298
|
+
volumes:
|
|
299
|
+
- db-data:/var/lib/postgresql/data
|
|
300
|
+
- ./service-configs/postgres/ensure-databases.sh:/usr/local/bin/ensure-databases.sh:ro
|
|
301
|
+
command: >
|
|
302
|
+
bash -c "
|
|
303
|
+
docker-entrypoint.sh postgres &
|
|
304
|
+
until pg_isready -U $${POSTGRES_USER:-stapel}; do sleep 1; done
|
|
305
|
+
/usr/local/bin/ensure-databases.sh
|
|
306
|
+
wait
|
|
307
|
+
"
|
|
308
|
+
healthcheck:
|
|
309
|
+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-stapel}"]
|
|
310
|
+
interval: 5s
|
|
311
|
+
timeout: 5s
|
|
312
|
+
retries: 10
|
|
313
|
+
|
|
314
|
+
redis:
|
|
315
|
+
image: redis:7-alpine
|
|
316
|
+
restart: unless-stopped
|
|
317
|
+
volumes:
|
|
318
|
+
- redis-data:/data
|
|
319
|
+
|
|
320
|
+
{{BROKER_SERVICES}} nginx:
|
|
321
|
+
image: nginx:alpine
|
|
322
|
+
restart: unless-stopped
|
|
323
|
+
ports:
|
|
324
|
+
- "${HTTP_PORT:-80}:80"
|
|
325
|
+
volumes:
|
|
326
|
+
- ./service-configs/nginx:/etc/nginx/conf.d:ro
|
|
327
|
+
- static-content:/staticfiles:ro
|
|
328
|
+
- media-content:/media:ro
|
|
329
|
+
depends_on: []
|
|
330
|
+
|
|
331
|
+
volumes:
|
|
332
|
+
db-data:
|
|
333
|
+
redis-data:
|
|
334
|
+
{{BROKER_VOLUMES}} static-content:
|
|
335
|
+
media-content:
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
MICRO_COMPOSE_LOCAL = """\
|
|
339
|
+
include:
|
|
340
|
+
- docker-compose.base.yml
|
|
341
|
+
|
|
342
|
+
services:
|
|
343
|
+
# Add your services here.
|
|
344
|
+
# Run: stapel-new-service <name> --prefix svc-
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
MICRO_ENV_TEMPLATE = """\
|
|
348
|
+
# ─── Database ──────────────────────────────────────────────────────────────
|
|
349
|
+
POSTGRES_USER=stapel
|
|
350
|
+
POSTGRES_PASSWORD=change_me
|
|
351
|
+
POSTGRES_HOST=db
|
|
352
|
+
POSTGRES_PORT=5432
|
|
353
|
+
|
|
354
|
+
# ─── Redis ─────────────────────────────────────────────────────────────────
|
|
355
|
+
REDIS_URL=redis://redis:6379/0
|
|
356
|
+
|
|
357
|
+
{{BROKER_ENV}}
|
|
358
|
+
# ─── App ───────────────────────────────────────────────────────────────────
|
|
359
|
+
SECRET_KEY=change_me_to_a_long_random_string
|
|
360
|
+
JWT_SECRET_KEY=change_me_to_another_long_random_string
|
|
361
|
+
ALLOWED_HOSTS={domain}
|
|
362
|
+
SITE_URL={url}
|
|
363
|
+
|
|
364
|
+
# ─── Email ─────────────────────────────────────────────────────────────────
|
|
365
|
+
DEFAULT_FROM_EMAIL={company_name} <{company_email}>
|
|
366
|
+
EMAIL_HOST=smtp.example.com
|
|
367
|
+
EMAIL_PORT=587
|
|
368
|
+
EMAIL_HOST_USER=
|
|
369
|
+
EMAIL_HOST_PASSWORD=
|
|
370
|
+
|
|
371
|
+
# ─── Run command ────────────────────────────────────────────────────────────
|
|
372
|
+
RUN_CMD=gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 2
|
|
373
|
+
"""
|