codefortify-starter 1.0.0__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.
- codefortify_starter/__init__.py +7 -0
- codefortify_starter/__main__.py +6 -0
- codefortify_starter/cli.py +103 -0
- codefortify_starter/constants.py +48 -0
- codefortify_starter/generator.py +246 -0
- codefortify_starter/template_engine.py +63 -0
- codefortify_starter/templates/base_project/README.md.jinja +87 -0
- codefortify_starter/templates/base_project/apps/__init__.py +1 -0
- codefortify_starter/templates/base_project/apps/home/__init__.py +1 -0
- codefortify_starter/templates/base_project/apps/home/apps.py.jinja +7 -0
- codefortify_starter/templates/base_project/apps/home/templates/home/index.html.jinja +25 -0
- codefortify_starter/templates/base_project/apps/home/tests.py.jinja +17 -0
- codefortify_starter/templates/base_project/apps/home/urls.py.jinja +14 -0
- codefortify_starter/templates/base_project/apps/home/views.py.jinja +13 -0
- codefortify_starter/templates/base_project/core/__init__.py.jinja +7 -0
- codefortify_starter/templates/base_project/core/asgi.py.jinja +10 -0
- codefortify_starter/templates/base_project/core/env.py.jinja +46 -0
- codefortify_starter/templates/base_project/core/middleware/__init__.py +1 -0
- codefortify_starter/templates/base_project/core/middleware/exceptions.py +26 -0
- codefortify_starter/templates/base_project/core/middleware/security.py +12 -0
- codefortify_starter/templates/base_project/core/settings/__init__.py +1 -0
- codefortify_starter/templates/base_project/core/settings/base.py.jinja +160 -0
- codefortify_starter/templates/base_project/core/settings/dev.py.jinja +8 -0
- codefortify_starter/templates/base_project/core/settings/production.py.jinja +21 -0
- codefortify_starter/templates/base_project/core/templates/errors/400.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/403.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/404.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/405.html +8 -0
- codefortify_starter/templates/base_project/core/templates/errors/500.html +8 -0
- codefortify_starter/templates/base_project/core/urls.py.jinja +23 -0
- codefortify_starter/templates/base_project/core/views.py.jinja +56 -0
- codefortify_starter/templates/base_project/core/wsgi.py.jinja +10 -0
- codefortify_starter/templates/base_project/dot-env.example.jinja +66 -0
- codefortify_starter/templates/base_project/dot-gitignore +34 -0
- codefortify_starter/templates/base_project/manage.py.jinja +16 -0
- codefortify_starter/templates/base_project/static/css/main.css +19 -0
- codefortify_starter/templates/base_project/templates/base.html.jinja +19 -0
- codefortify_starter/templates/features/celery/apps/home/tasks.py.jinja +7 -0
- codefortify_starter/templates/features/celery/core/celery.py.jinja +16 -0
- codefortify_starter/templates/features/docker/DOCKER_README.md.jinja +67 -0
- codefortify_starter/templates/features/docker/Dockerfile.jinja +20 -0
- codefortify_starter/templates/features/docker/docker-compose.prod.yml.jinja +158 -0
- codefortify_starter/templates/features/docker/docker-compose.yml.jinja +208 -0
- codefortify_starter/templates/features/docker/docker_deploy.sh.jinja +132 -0
- codefortify_starter/templates/features/docker/dot-dockerignore +12 -0
- codefortify_starter/templates/features/docker/entrypoint.sh.jinja +95 -0
- codefortify_starter/templates/features/drf/apps/api/__init__.py +1 -0
- codefortify_starter/templates/features/drf/apps/api/apps.py.jinja +7 -0
- codefortify_starter/templates/features/drf/apps/api/serializers.py +7 -0
- codefortify_starter/templates/features/drf/apps/api/tests.py +11 -0
- codefortify_starter/templates/features/drf/apps/api/urls.py +11 -0
- codefortify_starter/templates/features/drf/apps/api/views.py +16 -0
- codefortify_starter/templates/features/htmx/templates/partials/example.html.jinja +5 -0
- codefortify_starter/validators.py +70 -0
- codefortify_starter-1.0.0.dist-info/METADATA +276 -0
- codefortify_starter-1.0.0.dist-info/RECORD +60 -0
- codefortify_starter-1.0.0.dist-info/WHEEL +5 -0
- codefortify_starter-1.0.0.dist-info/entry_points.txt +2 -0
- codefortify_starter-1.0.0.dist-info/licenses/LICENSE +22 -0
- codefortify_starter-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
4
|
+
PYTHONUNBUFFERED=1 \
|
|
5
|
+
PIP_NO_CACHE_DIR=1
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY requirements.txt /app/requirements.txt
|
|
10
|
+
RUN pip install --upgrade pip && pip install -r /app/requirements.txt
|
|
11
|
+
|
|
12
|
+
COPY . /app
|
|
13
|
+
|
|
14
|
+
RUN chmod +x /app/entrypoint.sh /app/docker_deploy.sh
|
|
15
|
+
|
|
16
|
+
EXPOSE 8000
|
|
17
|
+
|
|
18
|
+
ENTRYPOINT ["/app/entrypoint.sh"]
|
|
19
|
+
CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "120"]
|
|
20
|
+
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
name: [[ project_slug ]]
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
web:
|
|
5
|
+
build:
|
|
6
|
+
context: .
|
|
7
|
+
dockerfile: Dockerfile
|
|
8
|
+
command: gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers ${GUNICORN_WORKERS:-3} --timeout ${GUNICORN_TIMEOUT:-120}
|
|
9
|
+
env_file:
|
|
10
|
+
- .env
|
|
11
|
+
environment:
|
|
12
|
+
CODEFORTIFY_ENVIRONMENT: production
|
|
13
|
+
DEBUG: "False"
|
|
14
|
+
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
|
|
15
|
+
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
|
|
16
|
+
WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
|
|
17
|
+
DB_BACKEND: "[[ database ]]"
|
|
18
|
+
RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true}
|
|
19
|
+
COLLECT_STATIC: ${COLLECT_STATIC:-true}
|
|
20
|
+
[% if use_postgres %]
|
|
21
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
|
|
22
|
+
POSTGRES_HOST: db
|
|
23
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
24
|
+
[% elif use_mysql %]
|
|
25
|
+
DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
26
|
+
MYSQL_HOST: db
|
|
27
|
+
MYSQL_PORT: ${MYSQL_PORT:-3306}
|
|
28
|
+
[% else %]
|
|
29
|
+
DATABASE_URL: sqlite:////app/db.sqlite3
|
|
30
|
+
[% endif %]
|
|
31
|
+
[% if use_celery %]
|
|
32
|
+
REDIS_URL: redis://redis:6379/0
|
|
33
|
+
CELERY_BROKER_URL: redis://redis:6379/0
|
|
34
|
+
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
35
|
+
[% endif %]
|
|
36
|
+
volumes:
|
|
37
|
+
- static_data:/app/static_root
|
|
38
|
+
- media_data:/app/media
|
|
39
|
+
[% if use_postgres or use_mysql or use_celery %]
|
|
40
|
+
depends_on:
|
|
41
|
+
[% if use_postgres or use_mysql %]
|
|
42
|
+
db:
|
|
43
|
+
condition: service_healthy
|
|
44
|
+
[% endif %]
|
|
45
|
+
[% if use_celery %]
|
|
46
|
+
redis:
|
|
47
|
+
condition: service_healthy
|
|
48
|
+
[% endif %]
|
|
49
|
+
[% endif %]
|
|
50
|
+
ports:
|
|
51
|
+
- "${APP_PORT:-8000}:8000"
|
|
52
|
+
restart: unless-stopped
|
|
53
|
+
|
|
54
|
+
[% if use_postgres %]
|
|
55
|
+
db:
|
|
56
|
+
image: postgres:16-alpine
|
|
57
|
+
env_file:
|
|
58
|
+
- .env
|
|
59
|
+
environment:
|
|
60
|
+
POSTGRES_DB: ${POSTGRES_DB:-[[ project_slug ]]}
|
|
61
|
+
POSTGRES_USER: ${POSTGRES_USER:-[[ project_slug ]]}
|
|
62
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me}
|
|
63
|
+
volumes:
|
|
64
|
+
- postgres_data:/var/lib/postgresql/data
|
|
65
|
+
healthcheck:
|
|
66
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-[[ project_slug ]]} -d ${POSTGRES_DB:-[[ project_slug ]]}"]
|
|
67
|
+
interval: 10s
|
|
68
|
+
timeout: 5s
|
|
69
|
+
retries: 20
|
|
70
|
+
start_period: 10s
|
|
71
|
+
restart: unless-stopped
|
|
72
|
+
[% elif use_mysql %]
|
|
73
|
+
db:
|
|
74
|
+
image: mysql:8.4
|
|
75
|
+
env_file:
|
|
76
|
+
- .env
|
|
77
|
+
environment:
|
|
78
|
+
MYSQL_DATABASE: ${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
79
|
+
MYSQL_USER: ${MYSQL_USER:-[[ project_slug ]]}
|
|
80
|
+
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
|
|
81
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root-change-me}
|
|
82
|
+
volumes:
|
|
83
|
+
- mysql_data:/var/lib/mysql
|
|
84
|
+
healthcheck:
|
|
85
|
+
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-[[ project_slug ]]} -p${MYSQL_PASSWORD:-change-me}"]
|
|
86
|
+
interval: 10s
|
|
87
|
+
timeout: 5s
|
|
88
|
+
retries: 30
|
|
89
|
+
start_period: 20s
|
|
90
|
+
restart: unless-stopped
|
|
91
|
+
[% endif %]
|
|
92
|
+
[% if use_celery %]
|
|
93
|
+
redis:
|
|
94
|
+
image: redis:7-alpine
|
|
95
|
+
command: redis-server --appendonly yes
|
|
96
|
+
volumes:
|
|
97
|
+
- redis_data:/data
|
|
98
|
+
healthcheck:
|
|
99
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
100
|
+
interval: 10s
|
|
101
|
+
timeout: 5s
|
|
102
|
+
retries: 20
|
|
103
|
+
start_period: 5s
|
|
104
|
+
restart: unless-stopped
|
|
105
|
+
|
|
106
|
+
celery:
|
|
107
|
+
build:
|
|
108
|
+
context: .
|
|
109
|
+
dockerfile: Dockerfile
|
|
110
|
+
command: celery -A core worker -l info
|
|
111
|
+
env_file:
|
|
112
|
+
- .env
|
|
113
|
+
environment:
|
|
114
|
+
CODEFORTIFY_ENVIRONMENT: production
|
|
115
|
+
DEBUG: "False"
|
|
116
|
+
DB_BACKEND: "[[ database ]]"
|
|
117
|
+
WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
|
|
118
|
+
RUN_MIGRATIONS: "false"
|
|
119
|
+
COLLECT_STATIC: "false"
|
|
120
|
+
[% if use_postgres %]
|
|
121
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
|
|
122
|
+
POSTGRES_HOST: db
|
|
123
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
124
|
+
[% elif use_mysql %]
|
|
125
|
+
DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
126
|
+
MYSQL_HOST: db
|
|
127
|
+
MYSQL_PORT: ${MYSQL_PORT:-3306}
|
|
128
|
+
[% else %]
|
|
129
|
+
DATABASE_URL: sqlite:////app/db.sqlite3
|
|
130
|
+
[% endif %]
|
|
131
|
+
REDIS_URL: redis://redis:6379/0
|
|
132
|
+
CELERY_BROKER_URL: redis://redis:6379/0
|
|
133
|
+
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
134
|
+
[% if use_postgres or use_mysql %]
|
|
135
|
+
depends_on:
|
|
136
|
+
db:
|
|
137
|
+
condition: service_healthy
|
|
138
|
+
redis:
|
|
139
|
+
condition: service_healthy
|
|
140
|
+
[% else %]
|
|
141
|
+
depends_on:
|
|
142
|
+
redis:
|
|
143
|
+
condition: service_healthy
|
|
144
|
+
[% endif %]
|
|
145
|
+
restart: unless-stopped
|
|
146
|
+
[% endif %]
|
|
147
|
+
|
|
148
|
+
volumes:
|
|
149
|
+
[% if use_postgres %]
|
|
150
|
+
postgres_data:
|
|
151
|
+
[% elif use_mysql %]
|
|
152
|
+
mysql_data:
|
|
153
|
+
[% endif %]
|
|
154
|
+
[% if use_celery %]
|
|
155
|
+
redis_data:
|
|
156
|
+
[% endif %]
|
|
157
|
+
static_data:
|
|
158
|
+
media_data:
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
name: [[ project_slug ]]
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
web:
|
|
5
|
+
build:
|
|
6
|
+
context: .
|
|
7
|
+
dockerfile: Dockerfile
|
|
8
|
+
command: python manage.py runserver 0.0.0.0:8000
|
|
9
|
+
env_file:
|
|
10
|
+
- .env
|
|
11
|
+
environment:
|
|
12
|
+
CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
|
|
13
|
+
DEBUG: ${DEBUG:-True}
|
|
14
|
+
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0}
|
|
15
|
+
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:-http://localhost:8000,http://127.0.0.1:8000}
|
|
16
|
+
WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
|
|
17
|
+
DB_BACKEND: "[[ database ]]"
|
|
18
|
+
RUN_MIGRATIONS: "true"
|
|
19
|
+
COLLECT_STATIC: "false"
|
|
20
|
+
[% if use_postgres %]
|
|
21
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
|
|
22
|
+
POSTGRES_HOST: db
|
|
23
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
24
|
+
[% elif use_mysql %]
|
|
25
|
+
DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
26
|
+
MYSQL_HOST: db
|
|
27
|
+
MYSQL_PORT: ${MYSQL_PORT:-3306}
|
|
28
|
+
[% else %]
|
|
29
|
+
DATABASE_URL: sqlite:////app/db.sqlite3
|
|
30
|
+
[% endif %]
|
|
31
|
+
[% if use_celery %]
|
|
32
|
+
REDIS_URL: redis://redis:6379/0
|
|
33
|
+
CELERY_BROKER_URL: redis://redis:6379/0
|
|
34
|
+
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
35
|
+
[% endif %]
|
|
36
|
+
volumes:
|
|
37
|
+
- .:/app
|
|
38
|
+
- static_data:/app/static_root
|
|
39
|
+
- media_data:/app/media
|
|
40
|
+
[% if use_postgres or use_mysql or use_celery %]
|
|
41
|
+
depends_on:
|
|
42
|
+
[% if use_postgres or use_mysql %]
|
|
43
|
+
db:
|
|
44
|
+
condition: service_healthy
|
|
45
|
+
[% endif %]
|
|
46
|
+
[% if use_celery %]
|
|
47
|
+
redis:
|
|
48
|
+
condition: service_healthy
|
|
49
|
+
[% endif %]
|
|
50
|
+
[% endif %]
|
|
51
|
+
ports:
|
|
52
|
+
- "${APP_PORT:-8000}:8000"
|
|
53
|
+
restart: unless-stopped
|
|
54
|
+
|
|
55
|
+
[% if use_postgres %]
|
|
56
|
+
db:
|
|
57
|
+
image: postgres:16-alpine
|
|
58
|
+
env_file:
|
|
59
|
+
- .env
|
|
60
|
+
environment:
|
|
61
|
+
POSTGRES_DB: ${POSTGRES_DB:-[[ project_slug ]]}
|
|
62
|
+
POSTGRES_USER: ${POSTGRES_USER:-[[ project_slug ]]}
|
|
63
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me}
|
|
64
|
+
volumes:
|
|
65
|
+
- postgres_data:/var/lib/postgresql/data
|
|
66
|
+
healthcheck:
|
|
67
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-[[ project_slug ]]} -d ${POSTGRES_DB:-[[ project_slug ]]}"]
|
|
68
|
+
interval: 5s
|
|
69
|
+
timeout: 5s
|
|
70
|
+
retries: 20
|
|
71
|
+
start_period: 5s
|
|
72
|
+
restart: unless-stopped
|
|
73
|
+
[% elif use_mysql %]
|
|
74
|
+
db:
|
|
75
|
+
image: mysql:8.4
|
|
76
|
+
env_file:
|
|
77
|
+
- .env
|
|
78
|
+
environment:
|
|
79
|
+
MYSQL_DATABASE: ${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
80
|
+
MYSQL_USER: ${MYSQL_USER:-[[ project_slug ]]}
|
|
81
|
+
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change-me}
|
|
82
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root-change-me}
|
|
83
|
+
volumes:
|
|
84
|
+
- mysql_data:/var/lib/mysql
|
|
85
|
+
healthcheck:
|
|
86
|
+
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-[[ project_slug ]]} -p${MYSQL_PASSWORD:-change-me}"]
|
|
87
|
+
interval: 10s
|
|
88
|
+
timeout: 5s
|
|
89
|
+
retries: 30
|
|
90
|
+
start_period: 20s
|
|
91
|
+
restart: unless-stopped
|
|
92
|
+
[% endif %]
|
|
93
|
+
[% if use_celery %]
|
|
94
|
+
redis:
|
|
95
|
+
image: redis:7-alpine
|
|
96
|
+
command: redis-server --appendonly yes
|
|
97
|
+
volumes:
|
|
98
|
+
- redis_data:/data
|
|
99
|
+
healthcheck:
|
|
100
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
101
|
+
interval: 5s
|
|
102
|
+
timeout: 5s
|
|
103
|
+
retries: 20
|
|
104
|
+
start_period: 5s
|
|
105
|
+
restart: unless-stopped
|
|
106
|
+
|
|
107
|
+
celery:
|
|
108
|
+
build:
|
|
109
|
+
context: .
|
|
110
|
+
dockerfile: Dockerfile
|
|
111
|
+
command: celery -A core worker -l info
|
|
112
|
+
env_file:
|
|
113
|
+
- .env
|
|
114
|
+
environment:
|
|
115
|
+
CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
|
|
116
|
+
DEBUG: ${DEBUG:-True}
|
|
117
|
+
DB_BACKEND: "[[ database ]]"
|
|
118
|
+
WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
|
|
119
|
+
RUN_MIGRATIONS: "false"
|
|
120
|
+
COLLECT_STATIC: "false"
|
|
121
|
+
[% if use_postgres %]
|
|
122
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
|
|
123
|
+
POSTGRES_HOST: db
|
|
124
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
125
|
+
[% elif use_mysql %]
|
|
126
|
+
DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
127
|
+
MYSQL_HOST: db
|
|
128
|
+
MYSQL_PORT: ${MYSQL_PORT:-3306}
|
|
129
|
+
[% else %]
|
|
130
|
+
DATABASE_URL: sqlite:////app/db.sqlite3
|
|
131
|
+
[% endif %]
|
|
132
|
+
REDIS_URL: redis://redis:6379/0
|
|
133
|
+
CELERY_BROKER_URL: redis://redis:6379/0
|
|
134
|
+
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
135
|
+
volumes:
|
|
136
|
+
- .:/app
|
|
137
|
+
[% if use_postgres or use_mysql %]
|
|
138
|
+
depends_on:
|
|
139
|
+
db:
|
|
140
|
+
condition: service_healthy
|
|
141
|
+
redis:
|
|
142
|
+
condition: service_healthy
|
|
143
|
+
[% else %]
|
|
144
|
+
depends_on:
|
|
145
|
+
redis:
|
|
146
|
+
condition: service_healthy
|
|
147
|
+
[% endif %]
|
|
148
|
+
restart: unless-stopped
|
|
149
|
+
|
|
150
|
+
celery-beat:
|
|
151
|
+
build:
|
|
152
|
+
context: .
|
|
153
|
+
dockerfile: Dockerfile
|
|
154
|
+
command: celery -A core beat -l info --scheduler celery.beat:PersistentScheduler --pidfile=
|
|
155
|
+
profiles:
|
|
156
|
+
- beat
|
|
157
|
+
env_file:
|
|
158
|
+
- .env
|
|
159
|
+
environment:
|
|
160
|
+
CODEFORTIFY_ENVIRONMENT: ${CODEFORTIFY_ENVIRONMENT:-dev}
|
|
161
|
+
DEBUG: ${DEBUG:-True}
|
|
162
|
+
DB_BACKEND: "[[ database ]]"
|
|
163
|
+
WAIT_FOR_DB: "[% if use_postgres or use_mysql %]true[% else %]false[% endif %]"
|
|
164
|
+
RUN_MIGRATIONS: "false"
|
|
165
|
+
COLLECT_STATIC: "false"
|
|
166
|
+
[% if use_postgres %]
|
|
167
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-[[ project_slug ]]}:${POSTGRES_PASSWORD:-change-me}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-[[ project_slug ]]}
|
|
168
|
+
POSTGRES_HOST: db
|
|
169
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
170
|
+
[% elif use_mysql %]
|
|
171
|
+
DATABASE_URL: mysql://${MYSQL_USER:-[[ project_slug ]]}:${MYSQL_PASSWORD:-change-me}@db:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-[[ project_slug ]]}
|
|
172
|
+
MYSQL_HOST: db
|
|
173
|
+
MYSQL_PORT: ${MYSQL_PORT:-3306}
|
|
174
|
+
[% else %]
|
|
175
|
+
DATABASE_URL: sqlite:////app/db.sqlite3
|
|
176
|
+
[% endif %]
|
|
177
|
+
REDIS_URL: redis://redis:6379/0
|
|
178
|
+
CELERY_BROKER_URL: redis://redis:6379/0
|
|
179
|
+
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
180
|
+
volumes:
|
|
181
|
+
- .:/app
|
|
182
|
+
- celery_beat_data:/app/.celerybeat
|
|
183
|
+
[% if use_postgres or use_mysql %]
|
|
184
|
+
depends_on:
|
|
185
|
+
db:
|
|
186
|
+
condition: service_healthy
|
|
187
|
+
redis:
|
|
188
|
+
condition: service_healthy
|
|
189
|
+
[% else %]
|
|
190
|
+
depends_on:
|
|
191
|
+
redis:
|
|
192
|
+
condition: service_healthy
|
|
193
|
+
[% endif %]
|
|
194
|
+
restart: unless-stopped
|
|
195
|
+
[% endif %]
|
|
196
|
+
|
|
197
|
+
volumes:
|
|
198
|
+
[% if use_postgres %]
|
|
199
|
+
postgres_data:
|
|
200
|
+
[% elif use_mysql %]
|
|
201
|
+
mysql_data:
|
|
202
|
+
[% endif %]
|
|
203
|
+
[% if use_celery %]
|
|
204
|
+
redis_data:
|
|
205
|
+
celery_beat_data:
|
|
206
|
+
[% endif %]
|
|
207
|
+
static_data:
|
|
208
|
+
media_data:
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -Eeuo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="${SCRIPT_DIR}"
|
|
6
|
+
ENV_FILE="${PROJECT_ROOT}/.env"
|
|
7
|
+
ENV_EXAMPLE="${PROJECT_ROOT}/.env.example"
|
|
8
|
+
|
|
9
|
+
COMPOSE_FILE="docker-compose.yml"
|
|
10
|
+
if [ "${1:-}" = "--prod" ]; then
|
|
11
|
+
COMPOSE_FILE="docker-compose.prod.yml"
|
|
12
|
+
shift
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
ACTION="${1:-up}"
|
|
16
|
+
LOG_SERVICE="${2:-web}"
|
|
17
|
+
|
|
18
|
+
log() {
|
|
19
|
+
printf '[docker_deploy] %s\n' "$*"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
die() {
|
|
23
|
+
printf '[docker_deploy][error] %s\n' "$*" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
usage() {
|
|
28
|
+
cat <<EOF
|
|
29
|
+
Usage:
|
|
30
|
+
./docker_deploy.sh [--prod] [action] [service]
|
|
31
|
+
|
|
32
|
+
Actions:
|
|
33
|
+
up Build and start services, then run migrate/check
|
|
34
|
+
down Stop and remove services
|
|
35
|
+
restart Rebuild and restart services
|
|
36
|
+
ps Show container status
|
|
37
|
+
logs [svc] Tail logs (default service: web)
|
|
38
|
+
migrate Run Django migrations in web
|
|
39
|
+
check Run Django checks in web
|
|
40
|
+
test Run Django tests in web
|
|
41
|
+
shell Open shell in web container
|
|
42
|
+
EOF
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensure_docker() {
|
|
46
|
+
command -v docker >/dev/null 2>&1 || die "Docker is not installed."
|
|
47
|
+
docker info >/dev/null 2>&1 || die "Docker daemon is not reachable."
|
|
48
|
+
docker compose version >/dev/null 2>&1 || die "Docker Compose v2 is required."
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ensure_env_file() {
|
|
52
|
+
if [ ! -f "${ENV_FILE}" ]; then
|
|
53
|
+
[ -f "${ENV_EXAMPLE}" ] || die ".env is missing and .env.example was not found."
|
|
54
|
+
cp "${ENV_EXAMPLE}" "${ENV_FILE}"
|
|
55
|
+
log "Created .env from .env.example"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compose() {
|
|
60
|
+
docker compose --env-file "${ENV_FILE}" -f "${PROJECT_ROOT}/${COMPOSE_FILE}" "$@"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
start_services() {
|
|
64
|
+
log "Building images with ${COMPOSE_FILE}"
|
|
65
|
+
compose build
|
|
66
|
+
|
|
67
|
+
[% if use_postgres or use_mysql %]
|
|
68
|
+
log "Starting database"
|
|
69
|
+
compose up -d db
|
|
70
|
+
[% endif %]
|
|
71
|
+
[% if use_celery %]
|
|
72
|
+
log "Starting redis"
|
|
73
|
+
compose up -d redis
|
|
74
|
+
[% endif %]
|
|
75
|
+
|
|
76
|
+
log "Starting web"
|
|
77
|
+
compose up -d web
|
|
78
|
+
[% if use_celery %]
|
|
79
|
+
log "Starting celery worker"
|
|
80
|
+
compose up -d celery
|
|
81
|
+
[% endif %]
|
|
82
|
+
|
|
83
|
+
log "Running migrations"
|
|
84
|
+
compose exec -T web python manage.py migrate --noinput
|
|
85
|
+
|
|
86
|
+
log "Running Django checks"
|
|
87
|
+
compose exec -T web python manage.py check
|
|
88
|
+
|
|
89
|
+
log "Service status"
|
|
90
|
+
compose ps
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ensure_docker
|
|
94
|
+
ensure_env_file
|
|
95
|
+
|
|
96
|
+
case "${ACTION}" in
|
|
97
|
+
up)
|
|
98
|
+
start_services
|
|
99
|
+
;;
|
|
100
|
+
down)
|
|
101
|
+
compose down
|
|
102
|
+
;;
|
|
103
|
+
restart)
|
|
104
|
+
compose down
|
|
105
|
+
start_services
|
|
106
|
+
;;
|
|
107
|
+
ps)
|
|
108
|
+
compose ps
|
|
109
|
+
;;
|
|
110
|
+
logs)
|
|
111
|
+
compose logs -f "${LOG_SERVICE}"
|
|
112
|
+
;;
|
|
113
|
+
migrate)
|
|
114
|
+
compose exec -T web python manage.py migrate --noinput
|
|
115
|
+
;;
|
|
116
|
+
check)
|
|
117
|
+
compose exec -T web python manage.py check
|
|
118
|
+
;;
|
|
119
|
+
test)
|
|
120
|
+
compose exec -T web python manage.py test
|
|
121
|
+
;;
|
|
122
|
+
shell)
|
|
123
|
+
compose exec web bash
|
|
124
|
+
;;
|
|
125
|
+
-h|--help|help)
|
|
126
|
+
usage
|
|
127
|
+
;;
|
|
128
|
+
*)
|
|
129
|
+
usage
|
|
130
|
+
die "Unknown action: ${ACTION}"
|
|
131
|
+
;;
|
|
132
|
+
esac
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
log() {
|
|
5
|
+
printf '[entrypoint] %s\n' "$*"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
is_true() {
|
|
9
|
+
case "${1:-}" in
|
|
10
|
+
1|true|TRUE|True|yes|YES|on|ON) return 0 ;;
|
|
11
|
+
*) return 1 ;;
|
|
12
|
+
esac
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
wait_for_database() {
|
|
16
|
+
python - <<'PY'
|
|
17
|
+
import os
|
|
18
|
+
import time
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
backend = (os.environ.get("DB_BACKEND") or "").strip().lower()
|
|
22
|
+
timeout = int(os.environ.get("DB_WAIT_TIMEOUT", "120"))
|
|
23
|
+
|
|
24
|
+
if backend in {"", "sqlite"}:
|
|
25
|
+
raise SystemExit(0)
|
|
26
|
+
|
|
27
|
+
started = time.time()
|
|
28
|
+
last_error = "unknown error"
|
|
29
|
+
|
|
30
|
+
while True:
|
|
31
|
+
try:
|
|
32
|
+
if backend == "postgres":
|
|
33
|
+
import psycopg2
|
|
34
|
+
|
|
35
|
+
dsn = (os.environ.get("DATABASE_URL") or "").strip()
|
|
36
|
+
if dsn:
|
|
37
|
+
conn = psycopg2.connect(dsn)
|
|
38
|
+
else:
|
|
39
|
+
conn = psycopg2.connect(
|
|
40
|
+
host=os.environ.get("POSTGRES_HOST", "db"),
|
|
41
|
+
port=int(os.environ.get("POSTGRES_PORT", "5432")),
|
|
42
|
+
user=os.environ.get("POSTGRES_USER", "postgres"),
|
|
43
|
+
password=os.environ.get("POSTGRES_PASSWORD", ""),
|
|
44
|
+
dbname=os.environ.get("POSTGRES_DB", "postgres"),
|
|
45
|
+
)
|
|
46
|
+
conn.close()
|
|
47
|
+
raise SystemExit(0)
|
|
48
|
+
|
|
49
|
+
if backend == "mysql":
|
|
50
|
+
import pymysql
|
|
51
|
+
|
|
52
|
+
conn = pymysql.connect(
|
|
53
|
+
host=os.environ.get("MYSQL_HOST", "db"),
|
|
54
|
+
port=int(os.environ.get("MYSQL_PORT", "3306")),
|
|
55
|
+
user=os.environ.get("MYSQL_USER", "root"),
|
|
56
|
+
password=os.environ.get("MYSQL_PASSWORD", ""),
|
|
57
|
+
database=os.environ.get("MYSQL_DATABASE", ""),
|
|
58
|
+
connect_timeout=5,
|
|
59
|
+
)
|
|
60
|
+
conn.close()
|
|
61
|
+
raise SystemExit(0)
|
|
62
|
+
|
|
63
|
+
raise SystemExit(0)
|
|
64
|
+
except SystemExit:
|
|
65
|
+
raise
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
last_error = str(exc)
|
|
68
|
+
if time.time() - started > timeout:
|
|
69
|
+
print(f"database did not become ready within {timeout}s: {last_error}", file=sys.stderr)
|
|
70
|
+
raise SystemExit(1)
|
|
71
|
+
time.sleep(1)
|
|
72
|
+
PY
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mkdir -p /app/static_root /app/media
|
|
76
|
+
|
|
77
|
+
if is_true "${WAIT_FOR_DB:-true}"; then
|
|
78
|
+
log "Waiting for database backend ${DB_BACKEND:-sqlite}"
|
|
79
|
+
wait_for_database
|
|
80
|
+
log "Database is ready"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if is_true "${RUN_MIGRATIONS:-true}"; then
|
|
84
|
+
log "Running migrations"
|
|
85
|
+
python manage.py migrate --noinput
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if is_true "${COLLECT_STATIC:-false}"; then
|
|
89
|
+
log "Collecting static files"
|
|
90
|
+
python manage.py collectstatic --noinput
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
log "Starting: $*"
|
|
94
|
+
exec "$@"
|
|
95
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from django.urls import reverse
|
|
2
|
+
from rest_framework import status
|
|
3
|
+
from rest_framework.test import APITestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HealthApiTests(APITestCase):
|
|
7
|
+
def test_health_endpoint(self):
|
|
8
|
+
response = self.client.get(reverse("api:health"))
|
|
9
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
10
|
+
self.assertEqual(response.data["status"], "ok")
|
|
11
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from rest_framework.permissions import AllowAny
|
|
2
|
+
from rest_framework.response import Response
|
|
3
|
+
from rest_framework.views import APIView
|
|
4
|
+
|
|
5
|
+
from apps.api.serializers import HealthSerializer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HealthAPIView(APIView):
|
|
9
|
+
authentication_classes = []
|
|
10
|
+
permission_classes = [AllowAny]
|
|
11
|
+
|
|
12
|
+
def get(self, request):
|
|
13
|
+
serializer = HealthSerializer(data={"status": "ok", "service": "api"})
|
|
14
|
+
serializer.is_valid(raise_exception=True)
|
|
15
|
+
return Response(serializer.validated_data)
|
|
16
|
+
|