-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbook_api_stack.py
More file actions
174 lines (154 loc) · 6 KB
/
Copy pathbook_api_stack.py
File metadata and controls
174 lines (154 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
from constructs import Construct
from aws_cdk import (
CfnOutput,
Stack,
Duration,
aws_ec2 as _ec2,
aws_ecs as _ecs,
aws_ecs_patterns as _ecs_patterns,
aws_ecr as _ecr,
aws_route53 as _route53,
aws_certificatemanager as _cert_manager,
aws_route53_targets as _targets,
aws_elasticloadbalancingv2 as _elasticloadbalancingv2,
)
from src.config import settings
LOCAL_NETWORK = settings.LOCAL_NETWORK_CIDR
class ContainerizedGraphQLAPIStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
api_name = settings.APP_NAME.lower().replace(" ", "-")
api_resource_prefix = f"{api_name}-{settings.ENV_TYPE.lower()}"
image_name = settings.APP_NAME.lower().replace(" ", "")
# Environment variables required by container
env_var = {
"DB_URL": settings.DB_URL,
"DB_NAME": settings.DB_NAME,
"HOST": settings.HOST,
"PORT": str(settings.PORT), # must be string
}
# Use the bookapi image from private book-api ECR repository
repo = _ecr.Repository.from_repository_name(
self, image_name, repository_name=api_name
)
image = _ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
image=_ecs.EcrImage.from_ecr_repository(repository=repo),
container_name=api_resource_prefix,
container_port=settings.PORT,
environment=env_var, # required or else the service will crash,
)
# Create the VPC with two availability zones
vpc = _ec2.Vpc(
self,
"BookApiStackVPC",
max_azs=2, # Default is all AZs
)
# Uncomment the following to retrieve VPC from Name
# Hint: Look at VPC entries for details
# vpc = _ec2.Vpc.from_lookup(self, 'BookApiStackVPC', vpc_name="ExistingVPCName")
# Add the interface for ECR
vpc.add_interface_endpoint(
"EcrDockerEndpoint", service=_ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER
)
# Create Security Groups and attach to VPC
sg = _ec2.SecurityGroup(
self,
id=f"{api_resource_prefix}sg-1",
vpc=vpc,
allow_all_outbound=True,
description=f"{api_resource_prefix}-security-group",
)
# Option to allow any connection on HTTPS
# Use caution with this, it can open your API vulnerabilities
# Note: No SSH is permitted with this security group on any port
# and ingress for this stack is set to HTTPs only
# sg.add_ingress_rule(
# peer=_ec2.Peer.any_ipv4(),
# connection=_ec2.Port.tcp(443),
# description="https",
# )
cluster = _ecs.Cluster(
self,
f"{api_resource_prefix}-cluster",
vpc=vpc,
)
domain_name = (settings.DOMAIN_NAME or "").strip()
lb = _elasticloadbalancingv2.ApplicationLoadBalancer(
self,
f"{image_name}-load-balancer",
vpc=vpc,
internet_facing=True,
security_group=sg,
)
lb.set_attribute("idle_timeout.timeout_seconds", "300")
if LOCAL_NETWORK:
listener_port = 443 if domain_name else 80
sg.add_ingress_rule(
peer=_ec2.Peer.ipv4(LOCAL_NETWORK),
connection=_ec2.Port.tcp(listener_port),
description="https-local" if domain_name else "http-local",
)
if domain_name:
hosted_zone = _route53.HostedZone.from_lookup(
self, f"{image_name}-hosted-zone", domain_name=domain_name
)
certificate = _cert_manager.Certificate(
self,
f"{image_name}-certificate",
domain_name=f"*.{domain_name}",
validation=_cert_manager.CertificateValidation.from_dns(hosted_zone),
)
service = _ecs_patterns.ApplicationLoadBalancedFargateService(
self,
id=api_resource_prefix,
service_name=api_resource_prefix,
cluster=cluster,
cpu=256,
desired_count=1,
task_image_options=image,
memory_limit_mib=512,
certificate=certificate,
redirect_http=True,
open_listener=False,
security_groups=[sg],
load_balancer=lb,
protocol=_elasticloadbalancingv2.ApplicationProtocol.HTTPS,
)
record_target = _route53.RecordTarget.from_alias(
_targets.LoadBalancerTarget(service.load_balancer)
)
_route53.ARecord(
self,
f"{image_name}-dns-record",
zone=hosted_zone,
record_name="api",
ttl=Duration.minutes(1),
target=record_target,
)
service_url = f"https://api.{domain_name}"
else:
service = _ecs_patterns.ApplicationLoadBalancedFargateService(
self,
id=api_resource_prefix,
service_name=api_resource_prefix,
cluster=cluster,
cpu=256,
desired_count=1,
task_image_options=image,
memory_limit_mib=512,
open_listener=not LOCAL_NETWORK,
security_groups=[sg],
load_balancer=lb,
protocol=_elasticloadbalancingv2.ApplicationProtocol.HTTP,
)
service_url = f"http://{service.load_balancer.load_balancer_dns_name}"
service.target_group.configure_health_check(
path="/",
healthy_http_codes="200",
)
CfnOutput(
self,
"ServiceUrl",
value=service_url,
description="Book API base URL (HTTP on ALB DNS, or HTTPS when DOMAIN_NAME is set)",
)