fastforge-cli 0.0.1__py3-none-any.whl
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.
- fastforge/__init__.py +1 -0
- fastforge/cli.py +693 -0
- fastforge/infra_template/cookiecutter.json +11 -0
- fastforge/infra_template/hooks/post_gen_project.py +77 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/README.md +41 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.app.yml +27 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.elasticsearch.yml +32 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.fluentbit.yml +14 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.kafka.yml +20 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.logstash.yml +14 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.mongodb.yml +17 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.postgres.yml +21 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.vault.yml +19 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.vector-agent.yml +13 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.vector-aggregator.yml +9 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.yml +31 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/fluentbit/fluent-bit.conf +24 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/fluentbit/parsers.conf +5 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/logstash/pipeline/logstash.conf +31 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vault/config.hcl +10 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vault/policies/app-policy.hcl +7 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vector/vector-agent.toml +36 -0
- fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vector/vector-aggregator.toml +29 -0
- fastforge/template/cookiecutter.json +34 -0
- fastforge/template/hooks/post_gen_project.py +71 -0
- fastforge/template/{{cookiecutter.project_slug}}/.codeclimate.yml +40 -0
- fastforge/template/{{cookiecutter.project_slug}}/.dockerignore +15 -0
- fastforge/template/{{cookiecutter.project_slug}}/.env.staging +86 -0
- fastforge/template/{{cookiecutter.project_slug}}/.gitignore +15 -0
- fastforge/template/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +16 -0
- fastforge/template/{{cookiecutter.project_slug}}/Dockerfile +30 -0
- fastforge/template/{{cookiecutter.project_slug}}/README.md +254 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/exception_handlers.py +78 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/models/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/models/{{cookiecutter.model_name}}.py +22 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/routes/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/routes/health.py +20 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/api/routes/{{cookiecutter.model_name_plural}}.py +70 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/cache.py +63 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/config.py +112 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/db/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/db/models/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/db/models/{{cookiecutter.model_name}}.py +34 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/db/mongodb.py +23 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/db/sqlalchemy.py +14 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/dependencies.py +45 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/logging_config.py +84 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/main.py +106 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/middleware/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/middleware/logging_middleware.py +45 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/middleware/security_headers.py +18 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/repositories/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/repositories/{{cookiecutter.model_name}}_repository.py +172 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/secrets.py +101 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/services/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/services/{{cookiecutter.model_name}}_service.py +63 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/streaming/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/streaming/consumer.py +187 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/streaming/handler.py +31 -0
- fastforge/template/{{cookiecutter.project_slug}}/app/streaming/producer.py +151 -0
- fastforge/template/{{cookiecutter.project_slug}}/docker-compose.debug.yml +15 -0
- fastforge/template/{{cookiecutter.project_slug}}/docker-compose.yml +141 -0
- fastforge/template/{{cookiecutter.project_slug}}/pyproject.toml +100 -0
- fastforge/template/{{cookiecutter.project_slug}}/qodana.yaml +22 -0
- fastforge/template/{{cookiecutter.project_slug}}/sonar-project.properties +20 -0
- fastforge/template/{{cookiecutter.project_slug}}/tests/__init__.py +0 -0
- fastforge/template/{{cookiecutter.project_slug}}/tests/conftest.py +11 -0
- fastforge/template/{{cookiecutter.project_slug}}/tests/test_api.py +51 -0
- fastforge_cli-0.0.1.dist-info/METADATA +163 -0
- fastforge_cli-0.0.1.dist-info/RECORD +76 -0
- fastforge_cli-0.0.1.dist-info/WHEEL +5 -0
- fastforge_cli-0.0.1.dist-info/entry_points.txt +12 -0
- fastforge_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
- fastforge_cli-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project_slug": "my-fastapi-service",
|
|
3
|
+
|
|
4
|
+
"log_agent": ["none", "vector", "fluentbit"],
|
|
5
|
+
"log_aggregator": ["none", "vector", "logstash"],
|
|
6
|
+
"streaming": ["none", "enabled"],
|
|
7
|
+
"secrets": ["none", "vault"],
|
|
8
|
+
"database": ["none", "postgres", "mongodb"],
|
|
9
|
+
|
|
10
|
+
"_copy_without_render": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Post-generation hook for infrastructure template — removes unused service files."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
PROJECT_DIR = os.path.realpath(os.path.curdir)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def remove_path(rel_path: str) -> None:
|
|
10
|
+
abs_path = os.path.join(PROJECT_DIR, rel_path)
|
|
11
|
+
if os.path.isdir(abs_path):
|
|
12
|
+
shutil.rmtree(abs_path)
|
|
13
|
+
elif os.path.isfile(abs_path):
|
|
14
|
+
os.remove(abs_path)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── Log Agent ────────────────────────────────────────────────────────────────
|
|
18
|
+
log_agent = "{{ cookiecutter.log_agent }}"
|
|
19
|
+
log_aggregator = "{{ cookiecutter.log_aggregator }}"
|
|
20
|
+
|
|
21
|
+
if log_agent == "none":
|
|
22
|
+
remove_path("docker-compose.vector-agent.yml")
|
|
23
|
+
remove_path("docker-compose.fluentbit.yml")
|
|
24
|
+
elif log_agent == "vector":
|
|
25
|
+
remove_path("docker-compose.fluentbit.yml")
|
|
26
|
+
remove_path("fluentbit")
|
|
27
|
+
elif log_agent == "fluentbit":
|
|
28
|
+
remove_path("docker-compose.vector-agent.yml")
|
|
29
|
+
remove_path("vector/vector-agent.toml")
|
|
30
|
+
|
|
31
|
+
# ── Log Aggregator ───────────────────────────────────────────────────────────
|
|
32
|
+
if log_aggregator == "none":
|
|
33
|
+
remove_path("docker-compose.vector-aggregator.yml")
|
|
34
|
+
remove_path("docker-compose.logstash.yml")
|
|
35
|
+
remove_path("docker-compose.elasticsearch.yml")
|
|
36
|
+
elif log_aggregator == "vector":
|
|
37
|
+
remove_path("docker-compose.logstash.yml")
|
|
38
|
+
remove_path("logstash")
|
|
39
|
+
elif log_aggregator == "logstash":
|
|
40
|
+
remove_path("docker-compose.vector-aggregator.yml")
|
|
41
|
+
remove_path("vector/vector-aggregator.toml")
|
|
42
|
+
|
|
43
|
+
# Clean up empty vector dir if neither uses it
|
|
44
|
+
if log_agent != "vector" and log_aggregator != "vector":
|
|
45
|
+
remove_path("vector")
|
|
46
|
+
|
|
47
|
+
# Remove FluentBit if not used as agent
|
|
48
|
+
if log_agent != "fluentbit":
|
|
49
|
+
remove_path("fluentbit")
|
|
50
|
+
|
|
51
|
+
# Remove Logstash if not used as aggregator
|
|
52
|
+
if log_aggregator != "logstash":
|
|
53
|
+
remove_path("logstash")
|
|
54
|
+
|
|
55
|
+
# No log pipeline means no ES/Kibana needed
|
|
56
|
+
if log_agent == "none" and log_aggregator == "none":
|
|
57
|
+
remove_path("docker-compose.elasticsearch.yml")
|
|
58
|
+
|
|
59
|
+
# ── Kafka ────────────────────────────────────────────────────────────────────
|
|
60
|
+
# Kafka needed for streaming OR as log transport
|
|
61
|
+
needs_kafka = "{{ cookiecutter.streaming }}" != "none" or log_agent != "none"
|
|
62
|
+
if not needs_kafka:
|
|
63
|
+
remove_path("docker-compose.kafka.yml")
|
|
64
|
+
|
|
65
|
+
# ── Database ─────────────────────────────────────────────────────────────────
|
|
66
|
+
if "{{ cookiecutter.database }}" != "postgres":
|
|
67
|
+
remove_path("docker-compose.postgres.yml")
|
|
68
|
+
if "{{ cookiecutter.database }}" != "mongodb":
|
|
69
|
+
remove_path("docker-compose.mongodb.yml")
|
|
70
|
+
|
|
71
|
+
# ── Vault ────────────────────────────────────────────────────────────────────
|
|
72
|
+
if "{{ cookiecutter.secrets }}" != "vault":
|
|
73
|
+
remove_path("docker-compose.vault.yml")
|
|
74
|
+
remove_path("vault")
|
|
75
|
+
|
|
76
|
+
print("\n✅ Infrastructure stack generated: {{ cookiecutter.project_slug }}-infrastructure/")
|
|
77
|
+
print(" cd {{ cookiecutter.project_slug }}-infrastructure && docker compose up -d")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# {{ cookiecutter.project_slug }} — Infrastructure Stack
|
|
2
|
+
|
|
3
|
+
Docker Compose stack for supporting services (local development / staging).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
docker compose up -d
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Services
|
|
12
|
+
|
|
13
|
+
| Service | Port | Description |
|
|
14
|
+
|---------|------|-------------|
|
|
15
|
+
| App | 8000 | FastAPI application |
|
|
16
|
+
{%- if cookiecutter.log_agent == "vector" %}
|
|
17
|
+
| Vector Agent | — | Log collector (file → Kafka) |
|
|
18
|
+
{%- elif cookiecutter.log_agent == "fluentbit" %}
|
|
19
|
+
| Fluent Bit | — | Log collector (file → Kafka) |
|
|
20
|
+
{%- endif %}
|
|
21
|
+
{%- if cookiecutter.log_aggregator == "vector" %}
|
|
22
|
+
| Vector Aggregator | — | Log pipeline (Kafka → Elasticsearch) |
|
|
23
|
+
{%- elif cookiecutter.log_aggregator == "logstash" %}
|
|
24
|
+
| Logstash | — | Log pipeline (Kafka → Elasticsearch) |
|
|
25
|
+
{%- endif %}
|
|
26
|
+
{%- if cookiecutter.log_agent != "none" or cookiecutter.log_aggregator != "none" %}
|
|
27
|
+
| Elasticsearch | 9200 | Log storage |
|
|
28
|
+
| Kibana | 5601 | Log visualization |
|
|
29
|
+
{%- endif %}
|
|
30
|
+
{%- if cookiecutter.streaming != "none" or cookiecutter.log_agent != "none" %}
|
|
31
|
+
| Kafka | 9092 | Message broker |
|
|
32
|
+
{%- endif %}
|
|
33
|
+
{%- if cookiecutter.database == "postgres" %}
|
|
34
|
+
| PostgreSQL | 5432 | Database |
|
|
35
|
+
{%- endif %}
|
|
36
|
+
{%- if cookiecutter.database == "mongodb" %}
|
|
37
|
+
| MongoDB | 27017 | Database |
|
|
38
|
+
{%- endif %}
|
|
39
|
+
{%- if cookiecutter.secrets == "vault" %}
|
|
40
|
+
| Vault | 8200 | Secret management |
|
|
41
|
+
{%- endif %}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build:
|
|
4
|
+
context: ..
|
|
5
|
+
dockerfile: Dockerfile
|
|
6
|
+
container_name: {{ cookiecutter.project_slug }}-app
|
|
7
|
+
ports:
|
|
8
|
+
- "8000:8000"
|
|
9
|
+
environment:
|
|
10
|
+
- APP_ENV=development
|
|
11
|
+
volumes:
|
|
12
|
+
{%- if cookiecutter.log_agent != "none" %}
|
|
13
|
+
- app-logs:/var/log/app
|
|
14
|
+
{%- endif %}
|
|
15
|
+
- ../app:/code/app:ro
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
|
18
|
+
interval: 15s
|
|
19
|
+
timeout: 5s
|
|
20
|
+
retries: 3
|
|
21
|
+
|
|
22
|
+
{%- if cookiecutter.log_agent != "none" %}
|
|
23
|
+
|
|
24
|
+
volumes:
|
|
25
|
+
app-logs:
|
|
26
|
+
name: {{ cookiecutter.project_slug }}-app-logs
|
|
27
|
+
{%- endif %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
services:
|
|
2
|
+
elasticsearch:
|
|
3
|
+
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-elasticsearch
|
|
5
|
+
environment:
|
|
6
|
+
- discovery.type=single-node
|
|
7
|
+
- xpack.security.enabled=false
|
|
8
|
+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
9
|
+
ports:
|
|
10
|
+
- "9200:9200"
|
|
11
|
+
volumes:
|
|
12
|
+
- esdata:/var/lib/elasticsearch/data
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
|
|
15
|
+
interval: 30s
|
|
16
|
+
timeout: 10s
|
|
17
|
+
retries: 5
|
|
18
|
+
|
|
19
|
+
kibana:
|
|
20
|
+
image: docker.elastic.co/kibana/kibana:8.15.0
|
|
21
|
+
container_name: {{ cookiecutter.project_slug }}-kibana
|
|
22
|
+
environment:
|
|
23
|
+
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
|
|
24
|
+
ports:
|
|
25
|
+
- "5601:5601"
|
|
26
|
+
depends_on:
|
|
27
|
+
elasticsearch:
|
|
28
|
+
condition: service_healthy
|
|
29
|
+
|
|
30
|
+
volumes:
|
|
31
|
+
esdata:
|
|
32
|
+
name: {{ cookiecutter.project_slug }}-esdata
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.fluentbit.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
services:
|
|
2
|
+
fluentbit:
|
|
3
|
+
image: fluent/fluent-bit:3.1
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-fluentbit
|
|
5
|
+
volumes:
|
|
6
|
+
- ./fluentbit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro
|
|
7
|
+
- ./fluentbit/parsers.conf:/fluent-bit/etc/parsers.conf:ro
|
|
8
|
+
- app-logs:/var/log/app:ro
|
|
9
|
+
depends_on:
|
|
10
|
+
- kafka
|
|
11
|
+
|
|
12
|
+
volumes:
|
|
13
|
+
app-logs:
|
|
14
|
+
name: {{ cookiecutter.project_slug }}-app-logs
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.kafka.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
services:
|
|
2
|
+
kafka:
|
|
3
|
+
image: confluentinc/cp-kafka:7.7.0
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-kafka
|
|
5
|
+
environment:
|
|
6
|
+
KAFKA_NODE_ID: 1
|
|
7
|
+
KAFKA_PROCESS_ROLES: broker,controller
|
|
8
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
|
|
9
|
+
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
|
|
10
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
|
11
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
|
|
12
|
+
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
13
|
+
CLUSTER_ID: {{ cookiecutter.project_slug }}-kafka-cluster
|
|
14
|
+
ports:
|
|
15
|
+
- "9092:9092"
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ["CMD-SHELL", "kafka-broker-api-versions --bootstrap-server localhost:9092"]
|
|
18
|
+
interval: 30s
|
|
19
|
+
timeout: 10s
|
|
20
|
+
retries: 5
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.logstash.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
services:
|
|
2
|
+
logstash:
|
|
3
|
+
image: docker.elastic.co/logstash/logstash:8.15.0
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-logstash
|
|
5
|
+
volumes:
|
|
6
|
+
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
|
|
7
|
+
environment:
|
|
8
|
+
LS_JAVA_OPTS: "-Xms256m -Xmx256m"
|
|
9
|
+
XPACK_MONITORING_ENABLED: "false"
|
|
10
|
+
depends_on:
|
|
11
|
+
kafka:
|
|
12
|
+
condition: service_healthy
|
|
13
|
+
elasticsearch:
|
|
14
|
+
condition: service_healthy
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.mongodb.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
services:
|
|
2
|
+
mongodb:
|
|
3
|
+
image: mongo:7
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-mongodb
|
|
5
|
+
ports:
|
|
6
|
+
- "27017:27017"
|
|
7
|
+
volumes:
|
|
8
|
+
- mongodata:/data/db
|
|
9
|
+
healthcheck:
|
|
10
|
+
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
|
11
|
+
interval: 10s
|
|
12
|
+
timeout: 5s
|
|
13
|
+
retries: 5
|
|
14
|
+
|
|
15
|
+
volumes:
|
|
16
|
+
mongodata:
|
|
17
|
+
name: {{ cookiecutter.project_slug }}-mongodata
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.postgres.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-postgres
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_USER: postgres
|
|
7
|
+
POSTGRES_PASSWORD: postgres
|
|
8
|
+
POSTGRES_DB: {{ cookiecutter.project_slug | replace('-', '_') }}
|
|
9
|
+
ports:
|
|
10
|
+
- "5432:5432"
|
|
11
|
+
volumes:
|
|
12
|
+
- pgdata:/var/lib/postgresql/data
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
15
|
+
interval: 10s
|
|
16
|
+
timeout: 5s
|
|
17
|
+
retries: 5
|
|
18
|
+
|
|
19
|
+
volumes:
|
|
20
|
+
pgdata:
|
|
21
|
+
name: {{ cookiecutter.project_slug }}-pgdata
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/docker-compose.vault.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
services:
|
|
2
|
+
vault:
|
|
3
|
+
image: hashicorp/vault:1.17
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-vault
|
|
5
|
+
cap_add:
|
|
6
|
+
- IPC_LOCK
|
|
7
|
+
environment:
|
|
8
|
+
VAULT_DEV_ROOT_TOKEN_ID: dev-only-token
|
|
9
|
+
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
|
|
10
|
+
ports:
|
|
11
|
+
- "8200:8200"
|
|
12
|
+
volumes:
|
|
13
|
+
- ./vault/config.hcl:/vault/config/config.hcl:ro
|
|
14
|
+
- ./vault/policies:/vault/policies:ro
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD-SHELL", "vault status || true"]
|
|
17
|
+
interval: 10s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 3
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
services:
|
|
2
|
+
vector-agent:
|
|
3
|
+
image: timberio/vector:0.41.1-alpine
|
|
4
|
+
container_name: {{ cookiecutter.project_slug }}-vector-agent
|
|
5
|
+
volumes:
|
|
6
|
+
- ./vector/vector-agent.toml:/etc/vector/vector.toml:ro
|
|
7
|
+
- app-logs:/var/log/app:ro
|
|
8
|
+
depends_on:
|
|
9
|
+
- kafka
|
|
10
|
+
|
|
11
|
+
volumes:
|
|
12
|
+
app-logs:
|
|
13
|
+
name: {{ cookiecutter.project_slug }}-app-logs
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Infrastructure Stack — {{ cookiecutter.project_slug }}
|
|
2
|
+
# Usage:
|
|
3
|
+
# docker compose -f infrastructure/docker-compose.yml up -d
|
|
4
|
+
|
|
5
|
+
include:
|
|
6
|
+
- docker-compose.app.yml
|
|
7
|
+
{%- if cookiecutter.log_agent == "vector" %}
|
|
8
|
+
- docker-compose.vector-agent.yml
|
|
9
|
+
{%- elif cookiecutter.log_agent == "fluentbit" %}
|
|
10
|
+
- docker-compose.fluentbit.yml
|
|
11
|
+
{%- endif %}
|
|
12
|
+
{%- if cookiecutter.log_aggregator == "vector" %}
|
|
13
|
+
- docker-compose.vector-aggregator.yml
|
|
14
|
+
{%- elif cookiecutter.log_aggregator == "logstash" %}
|
|
15
|
+
- docker-compose.logstash.yml
|
|
16
|
+
{%- endif %}
|
|
17
|
+
{%- if cookiecutter.log_agent != "none" or cookiecutter.log_aggregator != "none" %}
|
|
18
|
+
- docker-compose.elasticsearch.yml
|
|
19
|
+
{%- endif %}
|
|
20
|
+
{%- if cookiecutter.streaming != "none" or cookiecutter.log_agent != "none" %}
|
|
21
|
+
- docker-compose.kafka.yml
|
|
22
|
+
{%- endif %}
|
|
23
|
+
{%- if cookiecutter.database == "postgres" %}
|
|
24
|
+
- docker-compose.postgres.yml
|
|
25
|
+
{%- endif %}
|
|
26
|
+
{%- if cookiecutter.database == "mongodb" %}
|
|
27
|
+
- docker-compose.mongodb.yml
|
|
28
|
+
{%- endif %}
|
|
29
|
+
{%- if cookiecutter.secrets == "vault" %}
|
|
30
|
+
- docker-compose.vault.yml
|
|
31
|
+
{%- endif %}
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/fluentbit/fluent-bit.conf
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[SERVICE]
|
|
2
|
+
Flush 1
|
|
3
|
+
Daemon Off
|
|
4
|
+
Log_Level info
|
|
5
|
+
Parsers_File parsers.conf
|
|
6
|
+
|
|
7
|
+
[INPUT]
|
|
8
|
+
Name tail
|
|
9
|
+
Path /var/log/app/*.log
|
|
10
|
+
Tag app.*
|
|
11
|
+
Parser json
|
|
12
|
+
Read_from_Head True
|
|
13
|
+
Refresh_Interval 5
|
|
14
|
+
Rotate_Wait 30
|
|
15
|
+
DB /fluent-bit/etc/tail.db
|
|
16
|
+
|
|
17
|
+
[OUTPUT]
|
|
18
|
+
Name kafka
|
|
19
|
+
Match app.*
|
|
20
|
+
Brokers kafka:9092
|
|
21
|
+
Topics app-logs
|
|
22
|
+
Format json
|
|
23
|
+
Timestamp_Key @timestamp
|
|
24
|
+
Retry_Limit 5
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
input {
|
|
2
|
+
kafka {
|
|
3
|
+
bootstrap_servers => "kafka:9092"
|
|
4
|
+
topics => ["app-logs"]
|
|
5
|
+
group_id => "logstash-aggregator"
|
|
6
|
+
codec => "json"
|
|
7
|
+
consumer_threads => 1
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
filter {
|
|
12
|
+
mutate {
|
|
13
|
+
add_field => {
|
|
14
|
+
"service" => "{{ cookiecutter.project_slug }}"
|
|
15
|
+
"indexed_at" => "%{@timestamp}"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if [environment] == "" {
|
|
20
|
+
mutate {
|
|
21
|
+
add_field => { "environment" => "unknown" }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
output {
|
|
27
|
+
elasticsearch {
|
|
28
|
+
hosts => ["http://elasticsearch:9200"]
|
|
29
|
+
index => "{{ cookiecutter.project_slug }}-logs-%{+YYYY.MM.dd}"
|
|
30
|
+
}
|
|
31
|
+
}
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vector/vector-agent.toml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Vector Agent — collects logs from file → forwards to Kafka
|
|
2
|
+
|
|
3
|
+
[sources.app_logs]
|
|
4
|
+
type = "file"
|
|
5
|
+
include = ["/var/log/app/*.log"]
|
|
6
|
+
read_from = "beginning"
|
|
7
|
+
fingerprint.strategy = "device_and_inode"
|
|
8
|
+
|
|
9
|
+
[transforms.parse_json]
|
|
10
|
+
type = "remap"
|
|
11
|
+
inputs = ["app_logs"]
|
|
12
|
+
source = '''
|
|
13
|
+
parsed, err = parse_json(.message)
|
|
14
|
+
if err == null {
|
|
15
|
+
. = merge(., parsed)
|
|
16
|
+
del(.message)
|
|
17
|
+
} else {
|
|
18
|
+
.raw_message = .message
|
|
19
|
+
del(.message)
|
|
20
|
+
}
|
|
21
|
+
.source = "vector-agent"
|
|
22
|
+
.pipeline = "file-to-kafka"
|
|
23
|
+
.log_file = .file
|
|
24
|
+
'''
|
|
25
|
+
|
|
26
|
+
[sinks.kafka]
|
|
27
|
+
type = "kafka"
|
|
28
|
+
inputs = ["parse_json"]
|
|
29
|
+
bootstrap_servers = "kafka:9092"
|
|
30
|
+
topic = "app-logs"
|
|
31
|
+
encoding.codec = "json"
|
|
32
|
+
|
|
33
|
+
[sinks.kafka.buffer]
|
|
34
|
+
type = "disk"
|
|
35
|
+
max_size = 268435456
|
|
36
|
+
when_full = "block"
|
fastforge/infra_template/{{cookiecutter.project_slug}}-infrastructure/vector/vector-aggregator.toml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Vector Aggregator — consumes from Kafka → sinks to Elasticsearch
|
|
2
|
+
|
|
3
|
+
[sources.kafka]
|
|
4
|
+
type = "kafka"
|
|
5
|
+
bootstrap_servers = "kafka:9092"
|
|
6
|
+
group_id = "vector-aggregator"
|
|
7
|
+
topics = ["app-logs"]
|
|
8
|
+
decoding.codec = "json"
|
|
9
|
+
|
|
10
|
+
[transforms.enrich]
|
|
11
|
+
type = "remap"
|
|
12
|
+
inputs = ["kafka"]
|
|
13
|
+
source = '''
|
|
14
|
+
.environment = get_env_var("APP_ENV") ?? "unknown"
|
|
15
|
+
.service = "{{ cookiecutter.project_slug }}"
|
|
16
|
+
.indexed_at = now()
|
|
17
|
+
'''
|
|
18
|
+
|
|
19
|
+
[sinks.elasticsearch]
|
|
20
|
+
type = "elasticsearch"
|
|
21
|
+
inputs = ["enrich"]
|
|
22
|
+
endpoints = ["http://elasticsearch:9200"]
|
|
23
|
+
bulk.index = "{{ cookiecutter.project_slug }}-logs-%Y-%m-%d"
|
|
24
|
+
encoding.except_fields = ["source_type"]
|
|
25
|
+
|
|
26
|
+
[sinks.elasticsearch.buffer]
|
|
27
|
+
type = "disk"
|
|
28
|
+
max_size = 268435456
|
|
29
|
+
when_full = "block"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project_name": "my-fastapi-service",
|
|
3
|
+
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '-').replace('_', '-') }}",
|
|
4
|
+
"package_name": "{{ cookiecutter.project_slug.replace('-', '_') }}",
|
|
5
|
+
"description": "A production-grade FastAPI service",
|
|
6
|
+
"author_name": "Your Name",
|
|
7
|
+
"author_email": "you@example.com",
|
|
8
|
+
"python_version": "3.11",
|
|
9
|
+
"port": "8000",
|
|
10
|
+
|
|
11
|
+
"model_name": "item",
|
|
12
|
+
"model_name_class": "Item",
|
|
13
|
+
"model_name_plural": "items",
|
|
14
|
+
|
|
15
|
+
"database": ["none", "postgres", "mysql", "sqlite", "mongodb"],
|
|
16
|
+
"cache": ["none", "redis", "memcached", "in_memory"],
|
|
17
|
+
"streaming": ["none", "kafka", "rabbitmq", "redis_pubsub", "nats"],
|
|
18
|
+
"secrets": ["none", "vault", "aws_sm", "azure_kv", "gcp_sm"],
|
|
19
|
+
|
|
20
|
+
"logging": ["structlog", "none"],
|
|
21
|
+
"log_format": ["json", "console"],
|
|
22
|
+
"log_connector": ["stdout", "file"],
|
|
23
|
+
|
|
24
|
+
"quality_gate": ["none", "sonarqube", "sonarcloud", "qodana", "codeclimate"],
|
|
25
|
+
|
|
26
|
+
"docker": ["yes", "no"],
|
|
27
|
+
"docker_debug": ["yes", "no"],
|
|
28
|
+
|
|
29
|
+
"precommit": ["yes", "no"],
|
|
30
|
+
|
|
31
|
+
"_copy_without_render": [
|
|
32
|
+
"*.html"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Post-generation hook — removes files and directories for disabled features."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
PROJECT_DIR = os.path.realpath(os.path.curdir)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def remove_path(rel_path: str) -> None:
|
|
10
|
+
abs_path = os.path.join(PROJECT_DIR, rel_path)
|
|
11
|
+
if os.path.isdir(abs_path):
|
|
12
|
+
shutil.rmtree(abs_path)
|
|
13
|
+
elif os.path.isfile(abs_path):
|
|
14
|
+
os.remove(abs_path)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── Logging ──────────────────────────────────────────────────────────────────
|
|
18
|
+
if "{{ cookiecutter.logging }}" == "none":
|
|
19
|
+
remove_path("app/logging_config.py")
|
|
20
|
+
remove_path("app/middleware/logging_middleware.py")
|
|
21
|
+
|
|
22
|
+
# ── Database ─────────────────────────────────────────────────────────────────
|
|
23
|
+
database = "{{ cookiecutter.database }}"
|
|
24
|
+
if database == "none":
|
|
25
|
+
remove_path("app/db")
|
|
26
|
+
elif database in ("postgres", "mysql", "sqlite"):
|
|
27
|
+
remove_path("app/db/mongodb.py")
|
|
28
|
+
elif database == "mongodb":
|
|
29
|
+
remove_path("app/db/sqlalchemy.py")
|
|
30
|
+
|
|
31
|
+
# ── Cache ────────────────────────────────────────────────────────────────────
|
|
32
|
+
if "{{ cookiecutter.cache }}" == "none":
|
|
33
|
+
remove_path("app/cache.py")
|
|
34
|
+
|
|
35
|
+
# ── Streaming ────────────────────────────────────────────────────────────────
|
|
36
|
+
if "{{ cookiecutter.streaming }}" == "none":
|
|
37
|
+
remove_path("app/streaming")
|
|
38
|
+
|
|
39
|
+
# ── Secrets ──────────────────────────────────────────────────────────────────
|
|
40
|
+
if "{{ cookiecutter.secrets }}" == "none":
|
|
41
|
+
remove_path("app/secrets.py")
|
|
42
|
+
|
|
43
|
+
# ── Quality Gate ─────────────────────────────────────────────────────────────
|
|
44
|
+
quality_gate = "{{ cookiecutter.quality_gate }}"
|
|
45
|
+
if quality_gate != "sonarqube" and quality_gate != "sonarcloud":
|
|
46
|
+
remove_path("sonar-project.properties")
|
|
47
|
+
if quality_gate != "qodana":
|
|
48
|
+
remove_path("qodana.yaml")
|
|
49
|
+
if quality_gate != "codeclimate":
|
|
50
|
+
remove_path(".codeclimate.yml")
|
|
51
|
+
|
|
52
|
+
# ── Docker ───────────────────────────────────────────────────────────────────
|
|
53
|
+
if "{{ cookiecutter.docker }}" != "yes":
|
|
54
|
+
remove_path("Dockerfile")
|
|
55
|
+
remove_path(".dockerignore")
|
|
56
|
+
remove_path("docker-compose.yml")
|
|
57
|
+
remove_path("docker-compose.debug.yml")
|
|
58
|
+
elif "{{ cookiecutter.docker_debug }}" != "yes":
|
|
59
|
+
remove_path("docker-compose.debug.yml")
|
|
60
|
+
|
|
61
|
+
# ── Pre-commit ───────────────────────────────────────────────────────────────
|
|
62
|
+
if "{{ cookiecutter.precommit }}" != "yes":
|
|
63
|
+
remove_path(".pre-commit-config.yaml")
|
|
64
|
+
|
|
65
|
+
# ── Init git repo ────────────────────────────────────────────────────────────
|
|
66
|
+
os.system("git init -q")
|
|
67
|
+
os.system("git add .")
|
|
68
|
+
os.system('git commit -q -m "Initial project from FastForge"')
|
|
69
|
+
|
|
70
|
+
print("\n✅ {{ cookiecutter.project_name }} generated successfully!")
|
|
71
|
+
print(" cd {{ cookiecutter.project_slug }} && pip install -e '.[dev]'")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
version: "2"
|
|
2
|
+
|
|
3
|
+
plugins:
|
|
4
|
+
pep8:
|
|
5
|
+
enabled: true
|
|
6
|
+
radon:
|
|
7
|
+
enabled: true
|
|
8
|
+
config:
|
|
9
|
+
threshold: "C"
|
|
10
|
+
bandit:
|
|
11
|
+
enabled: true
|
|
12
|
+
|
|
13
|
+
checks:
|
|
14
|
+
argument-count:
|
|
15
|
+
config:
|
|
16
|
+
threshold: 5
|
|
17
|
+
complex-logic:
|
|
18
|
+
config:
|
|
19
|
+
threshold: 4
|
|
20
|
+
file-lines:
|
|
21
|
+
config:
|
|
22
|
+
threshold: 300
|
|
23
|
+
method-complexity:
|
|
24
|
+
config:
|
|
25
|
+
threshold: 10
|
|
26
|
+
method-count:
|
|
27
|
+
config:
|
|
28
|
+
threshold: 15
|
|
29
|
+
method-lines:
|
|
30
|
+
config:
|
|
31
|
+
threshold: 30
|
|
32
|
+
return-statements:
|
|
33
|
+
config:
|
|
34
|
+
threshold: 4
|
|
35
|
+
|
|
36
|
+
exclude_patterns:
|
|
37
|
+
- "tests/"
|
|
38
|
+
- ".venv/"
|
|
39
|
+
- "__pycache__/"
|
|
40
|
+
- "migrations/"
|