Skip to content

Commit 9ba7313

Browse files
Fix long-running ECS/Fargate Tasks (#2620)
* Use AWS SSM Parameter Store to handle environment variables * Use focused policy for read access * Update documentation * Add flag for create_rds_proxy * set default value of create_rds_proxy to false * Populate Zappa/Lambda environment variables from ssm/parameter store * Update documentation * Update example * add default configurations * add security group db from lambda * fix load-data task by adding a --fixture-path flag * fix ecs tasks by introducing ecs-* make targets * change ecs run steps * remove ecs-* and clean code * add --no-cache * use call_command * add test for --fixture-path * Update code * Update backend/wsgi.py --------- Co-authored-by: Arkadii Yakovets <[email protected]> Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent 3bb3ae5 commit 9ba7313

File tree

8 files changed

+93
-38
lines changed

8 files changed

+93
-38
lines changed

backend/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@ create-superuser:
2424
@CMD="python manage.py createsuperuser" $(MAKE) exec-backend-command-it
2525

2626
exec-backend-command:
27+
ifeq ($(EXEC_MODE),direct)
28+
@$(CMD)
29+
else
2730
@docker exec -i nest-backend $(CMD)
31+
endif
2832

2933
exec-backend-command-it:
34+
ifeq ($(EXEC_MODE),direct)
35+
@$(CMD)
36+
else
3037
@docker exec -it nest-backend $(CMD) 2>/dev/null
38+
endif
3139

3240
exec-db-command-it:
3341
@docker exec -it nest-db $(CMD)

backend/apps/common/management/commands/load_data.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,23 @@
1010
class Command(BaseCommand):
1111
help = "Load OWASP Nest data."
1212

13+
def add_arguments(self, parser) -> None:
14+
"""Add command-line arguments to the parser.
15+
16+
Args:
17+
parser (argparse.ArgumentParser): The argument parser instance.
18+
19+
"""
20+
parser.add_argument(
21+
"--fixture-path",
22+
default="data/nest.json.gz",
23+
required=False,
24+
type=str,
25+
help="Path to the fixture file",
26+
)
27+
1328
def handle(self, *_args, **_options) -> None:
1429
"""Load data into the OWASP Nest application."""
1530
with index.disable_indexing(), transaction.atomic():
1631
# Run loaddata
17-
call_command("loaddata", "data/nest.json.gz", "-v", "3")
32+
call_command("loaddata", _options["fixture_path"], "-v", "3")

backend/docker/Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,20 @@ RUN --mount=type=cache,target=${POETRY_CACHE_DIR},uid=${OWASP_UID},gid=${OWASP_G
3838
COPY apps apps
3939
COPY docker/entrypoint.sh entrypoint.sh
4040
COPY manage.py wsgi.py ./
41+
COPY Makefile Makefile
4142
COPY settings settings
4243
COPY static static
4344
COPY templates templates
4445

46+
# Required to run make targets
47+
RUN ln -s . backend
48+
4549
FROM python:3.13.7-alpine
4650

4751
ARG OWASP_GID
4852
ARG OWASP_UID
4953

50-
RUN apk update && \
54+
RUN apk update && apk --no-cache add make && \
5155
addgroup -S -g ${OWASP_GID} owasp && \
5256
adduser -S -h /home/owasp -u ${OWASP_UID} -G owasp owasp
5357

backend/tests/apps/common/management/commands/load_data_test.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import contextlib
22
from unittest.mock import MagicMock, patch
33

4-
from apps.common.management.commands.load_data import Command
4+
from django.core.management import call_command
55

66

77
class TestLoadDataCommand:
@@ -26,8 +26,7 @@ def test_handle(
2626
mock_unregister.return_value = None
2727
mock_register.return_value = None
2828

29-
command = Command()
30-
command.handle()
29+
call_command("load_data")
3130

3231
mock_unregister.assert_called_once()
3332
mock_register.assert_called_once()
@@ -36,6 +35,38 @@ def test_handle(
3635

3736
mock_atomic.assert_called_once()
3837

38+
@patch("apps.core.utils.index.DisableIndexing.unregister_indexes")
39+
@patch("apps.core.utils.index.DisableIndexing.register_indexes")
40+
@patch("apps.common.management.commands.load_data.call_command")
41+
@patch("apps.common.management.commands.load_data.transaction.atomic")
42+
def test_handle_with_custom_fixture_path(
43+
self,
44+
mock_atomic,
45+
mock_call_command,
46+
mock_register,
47+
mock_unregister,
48+
):
49+
mock_model = MagicMock()
50+
mock_app_config = MagicMock()
51+
mock_app_config.get_models.return_value = [mock_model]
52+
53+
mock_atomic.return_value.__enter__ = MagicMock()
54+
mock_atomic.return_value.__exit__ = MagicMock()
55+
56+
mock_unregister.return_value = None
57+
mock_register.return_value = None
58+
59+
call_command("load_data", fixture_path="custom/path/to/fixture.json.gz")
60+
61+
mock_unregister.assert_called_once()
62+
mock_register.assert_called_once()
63+
64+
mock_call_command.assert_called_once_with(
65+
"loaddata", "custom/path/to/fixture.json.gz", "-v", "3"
66+
)
67+
68+
mock_atomic.assert_called_once()
69+
3970
@patch("apps.core.utils.index.DisableIndexing.unregister_indexes")
4071
@patch("apps.core.utils.index.DisableIndexing.register_indexes")
4172
@patch("apps.common.management.commands.load_data.call_command")
@@ -50,13 +81,12 @@ def test_handle_with_exception_during_call_command(
5081
"""Test that indexing is re-enabled even if call_command fails."""
5182
mock_call_command.side_effect = Exception("Call command failed")
5283

53-
command = Command()
5484
with patch("contextlib.suppress") as mock_suppress:
5585
mock_suppress.return_value.__enter__ = MagicMock()
5686
mock_suppress.return_value.__exit__ = MagicMock()
5787

5888
with contextlib.suppress(Exception):
59-
command.handle()
89+
call_command("load_data")
6090

6191
mock_unregister.assert_called_once()
6292
mock_register.assert_called_once()

backend/wsgi.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
"""WSGI config for OWASP Nest project."""
22

33
import os
4+
from pathlib import Path
45

6+
import boto3
57

6-
def _populate_environ_from_ssm():
7-
ssm_param_path = os.getenv("AWS_SYSTEMS_MANAGER_PARAM_STORE_PATH")
8-
if not ssm_param_path:
9-
return
10-
11-
from pathlib import Path
128

13-
import boto3
9+
def populate_environ_from_ssm():
10+
"""Populate environment variables from AWS Systems Manager Parameter Store."""
11+
if not (ssm_param_path := os.getenv("AWS_SYSTEMS_MANAGER_PARAM_STORE_PATH")):
12+
return
1413

1514
client = boto3.client("ssm")
1615
paginator = client.get_paginator("get_parameters_by_path")
@@ -21,7 +20,7 @@ def _populate_environ_from_ssm():
2120
os.environ[Path(param["Name"]).name] = param["Value"]
2221

2322

24-
_populate_environ_from_ssm()
23+
populate_environ_from_ssm()
2524

2625
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.local")
2726
os.environ.setdefault("DJANGO_CONFIGURATION", "Local")

infrastructure/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,12 @@ Migrate and load data into the new database.
158158
3. **Run ECS Tasks**:
159159
- Head over to Elastic Container Service in the AWS Console.
160160
- Click on `owasp-nest-staging-migrate` in `Task Definitions` section.
161+
- Select the task definition revision.
161162
- Click Deploy > Run Task.
162163
- Use the following configuration:
163-
- Task details
164-
- Task definition revision: LATEST
165164
- Networking:
166165
- VPC: owasp-nest-staging-vpc
166+
- Subnets: subnets will be auto-selected due to VPC selection.
167167
- Security group name: select all with `owasp-nest-staging-` prefix.
168168
(*Note*: temporary step, will be further improved)
169169
- Click "Create"

infrastructure/modules/ecs/main.tf

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ module "sync_data_task" {
120120
source = "./modules/task"
121121

122122
aws_region = var.aws_region
123-
command = ["python", "manage.py", "sync-data"]
123+
command = ["/bin/sh", "-c", "EXEC_MODE=direct make sync-data"]
124124
common_tags = var.common_tags
125125
container_parameters_arns = var.container_parameters_arns
126126
cpu = var.sync_data_task_cpu
@@ -140,8 +140,16 @@ module "sync_data_task" {
140140
module "owasp_update_project_health_metrics_task" {
141141
source = "./modules/task"
142142

143-
aws_region = var.aws_region
144-
command = ["/bin/sh", "-c", "python manage.py owasp-update-project-health-requirements && python manage.py owasp-update-project-health-metrics"]
143+
aws_region = var.aws_region
144+
command = [
145+
"/bin/sh",
146+
"-c",
147+
<<-EOT
148+
set -e
149+
EXEC_MODE=direct make owasp-update-project-health-requirements
150+
EXEC_MODE=direct make owasp-update-project-health-metrics
151+
EOT
152+
]
145153
common_tags = var.common_tags
146154
container_parameters_arns = var.container_parameters_arns
147155
cpu = var.update_project_health_metrics_task_cpu
@@ -162,7 +170,7 @@ module "owasp_update_project_health_scores_task" {
162170
source = "./modules/task"
163171

164172
aws_region = var.aws_region
165-
command = ["python", "manage.py", "owasp-update-project-health-scores"]
173+
command = ["/bin/sh", "-c", "EXEC_MODE=direct make owasp-update-project-health-scores"]
166174
common_tags = var.common_tags
167175
container_parameters_arns = var.container_parameters_arns
168176
cpu = var.update_project_health_scores_task_cpu
@@ -183,7 +191,7 @@ module "migrate_task" {
183191
source = "./modules/task"
184192

185193
aws_region = var.aws_region
186-
command = ["python", "manage.py", "migrate"]
194+
command = ["/bin/sh", "-c", "EXEC_MODE=direct make migrate"]
187195
common_tags = var.common_tags
188196
container_parameters_arns = var.container_parameters_arns
189197
cpu = var.migrate_task_cpu
@@ -210,7 +218,7 @@ module "load_data_task" {
210218
pip install --target=/tmp/awscli-packages awscli
211219
export PYTHONPATH="/tmp/awscli-packages:$PYTHONPATH"
212220
python /tmp/awscli-packages/bin/aws s3 cp s3://${var.fixtures_s3_bucket}/nest.json.gz /tmp/nest.json.gz
213-
python manage.py loaddata /tmp/nest.json.gz -v 3
221+
python manage.py load_data --fixture-path /tmp/nest.json.gz
214222
EOT
215223
]
216224
common_tags = var.common_tags
@@ -231,17 +239,8 @@ module "load_data_task" {
231239
module "index_data_task" {
232240
source = "./modules/task"
233241

234-
aws_region = var.aws_region
235-
command = [
236-
"/bin/sh",
237-
"-c",
238-
<<-EOT
239-
set -e
240-
python manage.py algolia_reindex
241-
python manage.py algolia_update_replicas
242-
python manage.py algolia_update_synonyms
243-
EOT
244-
]
242+
aws_region = var.aws_region
243+
command = ["/bin/sh", "-c", "EXEC_MODE=direct make index-data"]
245244
common_tags = var.common_tags
246245
container_parameters_arns = var.container_parameters_arns
247246
cpu = var.index_data_task_cpu

infrastructure/modules/ecs/variables.tf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ variable "migrate_task_cpu" {
6868
variable "migrate_task_memory" {
6969
description = "The memory for the migrate task"
7070
type = string
71-
default = "2048"
71+
default = "1024"
7272
}
7373

7474
variable "private_subnet_ids" {
@@ -90,7 +90,7 @@ variable "sync_data_task_cpu" {
9090
variable "sync_data_task_memory" {
9191
description = "The memory for the sync-data task"
9292
type = string
93-
default = "2048"
93+
default = "1024"
9494
}
9595

9696
variable "update_project_health_metrics_task_cpu" {
@@ -102,7 +102,7 @@ variable "update_project_health_metrics_task_cpu" {
102102
variable "update_project_health_metrics_task_memory" {
103103
description = "The memory for the update-project-health-metrics task"
104104
type = string
105-
default = "2048"
105+
default = "1024"
106106
}
107107

108108
variable "update_project_health_scores_task_cpu" {
@@ -114,5 +114,5 @@ variable "update_project_health_scores_task_cpu" {
114114
variable "update_project_health_scores_task_memory" {
115115
description = "The memory for the update-project-health-scores task"
116116
type = string
117-
default = "2048"
117+
default = "1024"
118118
}

0 commit comments

Comments
 (0)