Skip to content
This repository was archived by the owner on Mar 27, 2023. It is now read-only.

Commit 1f84226

Browse files
committed
Merge branch '42-refactor-single-cdk-construct-to-nested-stacks' into 'develop'
Resolve "Split single core.Construct into nested Stacks" Closes #42 See merge request verbose-equals-true/django-postgres-vue-gitlab-ecs!82
2 parents 75e94c8 + 29628c2 commit 1f84226

32 files changed

+10842
-2347
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ backend/celerybeat-schedule
1717
.pytest_cache
1818
.~lock.*
1919
notes/
20-
.vscode
20+
.vscode
21+
cdk.out

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Documentation for this project can be found here:
44

55
[https://verbose-equals-true.gitlab.io/django-postgres-vue-gitlab-ecs/](https://verbose-equals-true.gitlab.io/django-postgres-vue-gitlab-ecs/)
66

7-
87
## Architecture
98

109
![png](/architecture.png)
@@ -17,6 +16,13 @@ First, copy `.env.template` to a new file in the project's root directory called
1716

1817
Currently I am working on replacing CloudFormation with CDK for infrastructure and deployment.
1918

19+
To work with CDK, do the following:
20+
21+
- Make sure you are using at least version 10 of node: `nvm use 13`
22+
- Activate the virtual environment with `source awscdk/.env/bin/activate`
23+
- `pip install -e awscdk` to install CDK dependencies
24+
- run `cdk synth --app awscdk/app.py --output awscdk/cdk.out` and view the resulting JSON for the nested CloudFormation stacks in `awscdk/cdk.out`
25+
2026
### Social Authentication Keys
2127

2228
To use social sign on in development, you will need to create an application with the given provider.
@@ -50,7 +56,6 @@ docker-compose -f compose/docs.yml up --build
5056

5157
This will make the docs available at `http://localhost:8082/docs/`. Hot-reloading through websockets is supported, so changes will show up as they are saved in your code editor.
5258

53-
5459
### Access Django Shell in Jupyter Notebook
5560

5661
With all containers running, run the following commands:

awscdk/app.py

100644100755
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
from awscdk.cdk_app_root import ApplicationStack
77

88
# naming conventions, also used for ACM certs, DNS Records, resource naming
9-
# Note: dynamically generated resource names created in CDK are used
10-
# in GitLab CI, such as cluster name, task definitions, etc.
9+
# Dynamically generated resource names created in CDK are used in GitLab CI
10+
# such as cluster name, task definitions, etc.
1111
environment_name = f"{os.environ.get('ENVIRONMENT', 'dev')}"
1212
base_domain_name = os.environ.get("DOMAIN_NAME", "mysite.com")
13+
# if the the production environent subdomain should nott be included in the URL
14+
# redefine `full_domain_name` to `base_domain_name` for that environment
1315
full_domain_name = f"{environment_name}.{base_domain_name}" # dev.mysite.com
16+
# if environment_name == "prod":
17+
# full_domain_name = base_domain_name
1418
base_app_name = os.environ.get("APP_NAME", "mysite-com")
1519
full_app_name = f"{environment_name}-{base_app_name}" # dev-mysite-com
20+
aws_region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
1621

1722

1823
app = core.App()
@@ -24,7 +29,7 @@
2429
full_domain_name=full_domain_name,
2530
base_app_name=base_app_name,
2631
full_app_name=full_app_name,
27-
env={"region": "us-east-1"},
32+
env={"region": aws_region},
2833
)
2934

3035
# in order to be able to tag ECS resources, you need to go to

awscdk/awscdk/alb.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
from aws_cdk import (
2-
aws_iam as iam,
32
aws_ec2 as ec2,
4-
aws_route53 as route53,
5-
aws_certificatemanager as acm,
63
aws_elasticloadbalancingv2 as elbv2,
4+
aws_cloudformation as cloudformation,
75
core,
86
)
97

108

11-
class ApplicationLoadBalancerResources(core.Construct):
12-
def __init__(
13-
self,
14-
scope: core.Construct,
15-
id: str,
16-
hosted_zone: route53.IHostedZone,
17-
certificate: acm.ICertificate,
18-
vpc: ec2.IVpc,
19-
**kwargs
20-
) -> None:
9+
class AlbStack(cloudformation.NestedStack):
10+
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
2111
super().__init__(scope, id, **kwargs)
2212

2313
self.alb = elbv2.ApplicationLoadBalancer(
24-
self, "ALB", internet_facing=True, vpc=vpc
14+
self, "ALB", internet_facing=True, vpc=scope.vpc
2515
)
2616

2717
self.alb.connections.allow_from_any_ipv4(
@@ -37,7 +27,10 @@ def __init__(
3727
)
3828

3929
self.https_listener = self.alb.add_listener(
40-
"HTTPSListener", port=443, certificates=[certificate], open=True
30+
"HTTPSListener",
31+
port=443,
32+
certificates=[scope.certificate],
33+
open=True,
4134
)
4235

4336
# self.listener.add_redirect_response(
@@ -49,7 +42,8 @@ def __init__(
4942
"DefaultTargetGroup",
5043
port=80,
5144
protocol=elbv2.ApplicationProtocol.HTTP,
52-
vpc=vpc,
45+
vpc=scope.vpc,
46+
target_type=elbv2.TargetType.IP,
5347
)
5448

5549
self.listener.add_target_groups(
Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
aws-cdk.core==1.38.0
2-
aws-cdk.aws_certificatemanager==1.38.0
3-
aws-cdk.aws_secretsmanager==1.38.0
4-
aws-cdk.aws_route53==1.38.0
5-
aws-cdk.aws_s3==1.38.0
6-
aws_cdk.aws_s3_deployment==1.38.0
7-
aws-cdk.aws_cloudfront==1.38.0
8-
aws-cdk.aws_route53_targets==1.38.0
9-
aws-cdk.aws_ecr==1.38.0
10-
aws-cdk.aws_ec2==1.38.0
11-
aws-cdk.aws_rds==1.38.0
12-
aws-cdk.aws_ssm==1.38.0
13-
aws-cdk.aws_elasticache==1.38.0
14-
aws-cdk.aws_elasticloadbalancingv2==1.38.0
15-
aws-cdk.aws_ecs==1.38.0
16-
aws-cdk.aws_ecs_patterns==1.38.0
17-
aws-cdk.aws_autoscaling==1.38.0
18-
aws-cdk.aws_sqs==1.38.0
1+
aws-cdk.core==1.41.0
2+
aws-cdk.aws_cloudformation==1.41.0
3+
aws-cdk.aws_certificatemanager==1.41.0
4+
aws-cdk.aws_logs==1.41.0
5+
aws-cdk.aws_secretsmanager==1.41.0
6+
aws-cdk.aws_route53==1.41.0
7+
aws-cdk.aws_s3==1.41.0
8+
aws_cdk.aws_s3_deployment==1.41.0
9+
aws-cdk.aws_cloudfront==1.41.0
10+
aws-cdk.aws_route53_targets==1.41.0
11+
aws-cdk.aws_ecr==1.41.0
12+
aws-cdk.aws_ec2==1.41.0
13+
aws-cdk.aws_rds==1.41.0
14+
aws-cdk.aws_ssm==1.41.0
15+
aws-cdk.aws_elasticache==1.41.0
16+
aws-cdk.aws_elasticloadbalancingv2==1.41.0
17+
aws-cdk.aws_ecs==1.41.0
18+
aws-cdk.aws_ecs_patterns==1.41.0
19+
aws-cdk.aws_autoscaling==1.41.0
20+
aws-cdk.aws_sqs==1.41.0

awscdk/awscdk/backend.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
1-
import os
2-
31
from aws_cdk import (
42
core,
53
aws_ec2 as ec2,
64
aws_ecs as ecs,
7-
aws_ecs_patterns as ecs_patterns,
5+
aws_logs as logs,
6+
aws_cloudformation as cloudformation,
87
aws_elasticloadbalancingv2 as elbv2,
98
)
109

1110

12-
class Backend(core.Construct):
13-
def __init__(
14-
self,
15-
scope: core.Construct,
16-
id: str,
17-
image: ecs.AssetImage,
18-
https_listener: elbv2.IApplicationListener,
19-
cluster: ecs.ICluster,
20-
environment_variables: core.Construct,
21-
security_group: str,
22-
**kwargs,
23-
) -> None:
11+
class BackendServiceStack(cloudformation.NestedStack):
12+
def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
2413
super().__init__(
2514
scope, id, **kwargs,
2615
)
2716

2817
self.backend_task = ecs.FargateTaskDefinition(self, "BackendTask")
2918

3019
self.backend_task.add_container(
31-
"DjangoBackend",
32-
image=image,
33-
logging=ecs.LogDrivers.aws_logs(stream_prefix="Backend"),
34-
environment=environment_variables.regular_variables,
35-
secrets=environment_variables.secret_variables,
20+
"BackendContainer",
21+
image=scope.image,
22+
logging=ecs.LogDrivers.aws_logs(
23+
stream_prefix="BackendContainer",
24+
log_retention=logs.RetentionDays.ONE_WEEK,
25+
),
26+
environment=scope.variables.regular_variables,
27+
secrets=scope.variables.secret_variables,
3628
command=["/start_prod.sh"],
3729
)
3830

31+
scope.backend_assets_bucket.grant_read_write(
32+
self.backend_task.task_role
33+
)
34+
35+
for secret in [scope.variables.django_secret_key, scope.rds.db_secret]:
36+
secret.grant_read(self.backend_task.task_role)
37+
3938
port_mapping = ecs.PortMapping(
4039
container_port=8000, protocol=ecs.Protocol.TCP
4140
)
@@ -46,13 +45,15 @@ def __init__(
4645
"BackendService",
4746
task_definition=self.backend_task,
4847
assign_public_ip=True,
49-
cluster=cluster,
48+
cluster=scope.ecs.cluster,
5049
security_group=ec2.SecurityGroup.from_security_group_id(
51-
self, "BackendSecurityGroup", security_group_id=security_group
50+
self,
51+
"BackendServiceSecurityGroup",
52+
security_group_id=scope.vpc.vpc_default_security_group,
5253
),
5354
)
5455

55-
https_listener.add_targets(
56+
scope.https_listener.add_targets(
5657
"BackendTarget",
5758
port=80,
5859
targets=[self.backend_service],
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
from aws_cdk import aws_iam as iam, aws_s3 as s3, core
1+
from aws_cdk import (
2+
aws_iam as iam,
3+
aws_s3 as s3,
4+
core,
5+
aws_cloudformation as cloudformation,
6+
)
27

38

4-
class Assets(core.Construct):
5-
def __init__(
6-
self, scope: core.Construct, id: str, full_app_name: str, **kwargs
7-
) -> None:
9+
class BackendAssetsStack(cloudformation.NestedStack):
10+
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
811
super().__init__(scope, id, **kwargs)
912

1013
self.assets_bucket = s3.Bucket(
11-
self, "AssetsBucket", bucket_name=f"{full_app_name}-assets"
14+
self, "AssetsBucket", bucket_name=f"{scope.full_app_name}-assets"
1215
)
1316

1417
self.policy_statement = iam.PolicyStatement(

awscdk/awscdk/backend_tasks.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,76 @@
11
import os
22

3-
from aws_cdk import (
4-
core,
5-
aws_ecs as ecs,
6-
)
3+
from aws_cdk import core, aws_ecs as ecs, aws_cloudformation as cloudformation
74

5+
# These tasks are executed from manual GitLab CI jobs. The cluster is
6+
# specified with:
7+
# `aws ecs run-task --cluster ${ENVIRONMENT}-${APP_NAME}-cluster [...]`
8+
# TODO: consider making this more DRY
89

9-
class BackendTasks(core.Construct):
10-
def __init__(
11-
self,
12-
scope: core.Construct,
13-
id: str,
14-
image: ecs.AssetImage,
15-
cluster: ecs.ICluster,
16-
environment_variables: core.Construct,
17-
full_app_name: str,
18-
**kwargs,
19-
) -> None:
10+
11+
class BackendTasksStack(cloudformation.NestedStack):
12+
def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
2013
super().__init__(
2114
scope, id, **kwargs,
2215
)
2316

17+
# migrate
2418
self.migrate_task = ecs.FargateTaskDefinition(
25-
self, "MigrateTask", family=f"{full_app_name}-migrate"
19+
self, "MigrateTask", family=f"{scope.full_app_name}-migrate"
2620
)
2721

22+
for secret in [scope.variables.django_secret_key, scope.rds.db_secret]:
23+
secret.grant_read(self.migrate_task.task_role)
24+
2825
self.migrate_task.add_container(
2926
"MigrateCommand",
30-
image=image,
31-
environment=environment_variables.regular_variables,
32-
secrets=environment_variables.secret_variables,
27+
image=scope.image,
28+
environment=scope.variables.regular_variables,
29+
secrets=scope.variables.secret_variables,
3330
command=["python3", "manage.py", "migrate", "--no-input"],
3431
logging=ecs.LogDrivers.aws_logs(stream_prefix="MigrateCommand"),
3532
)
3633

34+
# collectstatic
3735
self.collectstatic_task = ecs.FargateTaskDefinition(
38-
self, "CollecstaticTask", family=f"{full_app_name}-collectstatic"
36+
self,
37+
"CollecstaticTask",
38+
family=f"{scope.full_app_name}-collectstatic",
3939
)
4040

41+
scope.backend_assets_bucket.grant_read_write(
42+
self.collectstatic_task.task_role
43+
)
44+
45+
for secret in [scope.variables.django_secret_key, scope.rds.db_secret]:
46+
secret.grant_read(self.collectstatic_task.task_role)
47+
4148
self.collectstatic_task.add_container(
4249
"CollecstaticCommand",
43-
image=image,
44-
environment=environment_variables.regular_variables,
45-
secrets=environment_variables.secret_variables,
50+
image=scope.image,
51+
environment=scope.variables.regular_variables,
52+
secrets=scope.variables.secret_variables,
4653
command=["python3", "manage.py", "collectstatic", "--no-input"],
4754
logging=ecs.LogDrivers.aws_logs(
4855
stream_prefix="CollectstaticCommand"
4956
),
5057
)
5158

59+
# createsuperuser
5260
self.create_superuser_task = ecs.FargateTaskDefinition(
5361
self,
5462
"CreateSuperuserTask",
55-
family=f"{full_app_name}-create-superuser",
63+
family=f"{scope.full_app_name}-create-superuser",
5664
)
5765

66+
for secret in [scope.variables.django_secret_key, scope.rds.db_secret]:
67+
secret.grant_read(self.create_superuser_task.task_role)
68+
5869
self.create_superuser_task.add_container(
5970
"CreateSuperuserCommand",
60-
image=image,
61-
environment=environment_variables.regular_variables,
62-
secrets=environment_variables.secret_variables.update(
71+
image=scope.image,
72+
environment=scope.variables.regular_variables,
73+
secrets=scope.variables.secret_variables.update(
6374
{
6475
"SUPERUSER_PASSWORD": os.environ.get(
6576
"SUPERUSER_PASSWORD", "password"

0 commit comments

Comments
 (0)