forked from cookiecutter/cookiecutter-django
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4032c57
commit 8f6cc96
Showing
22 changed files
with
1,016 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# pull official base image | ||
FROM python:3.12.0-slim-bookworm | ||
|
||
# set work directory | ||
WORKDIR /usr/src/app | ||
|
||
# set environment variables | ||
ENV PYTHONDONTWRITEBYTECODE 1 | ||
ENV PYTHONUNBUFFERED 1 | ||
|
||
# install APT dependencies | ||
RUN apt-get update \ | ||
&& apt-get install -y git gcc libpq-dev python3-dev postgresql libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ | ||
&& apt-get clean | ||
|
||
|
||
# install dependencies | ||
RUN pip install --upgrade pip | ||
COPY ./requirements/base.txt . | ||
COPY ./requirements/production.txt . | ||
RUN pip install -r production.txt | ||
|
||
# Create a user with UID 1000 and GID 1000 | ||
RUN groupadd -g 1000 appgroup && \ | ||
useradd -r -u 1000 -g appgroup appuser | ||
# Switch to this user | ||
USER 1000:1000 | ||
|
||
# copy project | ||
COPY . . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import subprocess | ||
|
||
import boto3 | ||
import click | ||
|
||
|
||
def get_terraform_output(output_name): | ||
try: | ||
return subprocess.check_output( | ||
["terraform", "output", "-state=../terraform/terraform.tfstate", output_name], | ||
text=True | ||
).strip() | ||
except Exception as e: | ||
print(f"Error fetching Terraform output {output_name}: {e}") | ||
return None | ||
|
||
def get_current_task_definition(client, cluster, service): | ||
response = client.describe_services(cluster=cluster, services=[service]) | ||
current_task_arn = response["services"][0]["taskDefinition"] | ||
return client.describe_task_definition(taskDefinition=current_task_arn) | ||
|
||
def run_collectstatic_task(client, cluster, task_definition_name): | ||
print("Running collectstatic task...") | ||
|
||
response = client.run_task( | ||
cluster=cluster, | ||
taskDefinition=task_definition_name, | ||
launchType="FARGATE", | ||
networkConfiguration=network_configuration, # Add the network configuration here | ||
) | ||
|
||
if response.get("tasks"): | ||
print(f"collectstatic task started with task ARN: {response['tasks'][0]['taskArn']}") | ||
else: | ||
print("Failed to start collectstatic task.") | ||
if response.get("failures"): | ||
for failure in response["failures"]: | ||
print(f"Reason: {failure['reason']}") | ||
|
||
subnets = [s.strip(' "\n[]') for s in get_terraform_output("subnets").split(",") if "subnet" in s] | ||
security_group = get_terraform_output("security_group").strip(' "\n') | ||
network_configuration = { | ||
"awsvpcConfiguration": { | ||
"subnets": subnets, | ||
"securityGroups": [security_group], | ||
"assignPublicIp": "ENABLED" | ||
} | ||
} | ||
|
||
@click.command() | ||
@click.option("--cluster", help="Name of the ECS cluster", required=True) | ||
@click.option("--service", help="Name of the ECS service", required=True) | ||
@click.option("--image", help="Docker image URL for the updated application", required=True) | ||
@click.option("--container-name", help="Name of the container to update", required=True) | ||
@click.option("--collectstatic-task", help="Name of the collectstatic task definition", default="django-collectstatic-task") | ||
def deploy(cluster, service, image, container_name, collectstatic_task): | ||
client = boto3.client("ecs") | ||
|
||
# Run the collectstatic task | ||
run_collectstatic_task(client, cluster, collectstatic_task) | ||
|
||
# Fetch the current task definition | ||
print("Fetching current task definition...") | ||
response = get_current_task_definition(client, cluster, service) | ||
|
||
# Iterate over container definitions and update the image for the matching container | ||
container_definitions = response["taskDefinition"]["containerDefinitions"] | ||
for container in container_definitions: | ||
if container["name"] == container_name: | ||
container["image"] = image | ||
print(f"Updated {container_name} image to: {image}") | ||
|
||
# Register a new task definition | ||
print("Registering new task definition...") | ||
response = client.register_task_definition( | ||
family=response["taskDefinition"]["family"], | ||
volumes=response["taskDefinition"]["volumes"], | ||
containerDefinitions=container_definitions, | ||
cpu="256", # Modify based on your needs | ||
memory="512", # Modify based on your needs | ||
networkMode="awsvpc", | ||
requiresCompatibilities=["FARGATE"], | ||
executionRoleArn="ecs_task_execution_role_prod", | ||
taskRoleArn="ecs_task_execution_role_prod" | ||
) | ||
new_task_arn = response["taskDefinition"]["taskDefinitionArn"] | ||
print(f"New task definition ARN: {new_task_arn}") | ||
|
||
# Update the service with the new task definition | ||
print("Updating ECS service with new task definition...") | ||
client.update_service( | ||
cluster=cluster, service=service, taskDefinition=new_task_arn, | ||
) | ||
print("Service updated!") | ||
|
||
|
||
if __name__ == "__main__": | ||
deploy() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
FROM nginx:1.25.3-alpine | ||
|
||
RUN rm /etc/nginx/conf.d/default.conf | ||
COPY nginx.conf /etc/nginx/conf.d | ||
EXPOSE 80 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
upstream todo { | ||
server localhost:8000; | ||
} | ||
|
||
server { | ||
|
||
listen 80; | ||
|
||
location /staticfiles/ { | ||
alias /efs/staticfiles/; # Path to static files on EFS | ||
} | ||
|
||
location / { | ||
proxy_pass http://todo; | ||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
proxy_set_header Host $host; | ||
proxy_redirect off; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# core | ||
|
||
provider "aws" { | ||
region = var.region | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Production VPC | ||
resource "aws_vpc" "production-vpc" { | ||
cidr_block = "10.0.0.0/16" | ||
enable_dns_support = true | ||
enable_dns_hostnames = true | ||
} | ||
|
||
# Public subnets | ||
resource "aws_subnet" "public-subnet-1" { | ||
cidr_block = var.public_subnet_1_cidr | ||
vpc_id = aws_vpc.production-vpc.id | ||
availability_zone = var.availability_zones[0] | ||
} | ||
resource "aws_subnet" "public-subnet-2" { | ||
cidr_block = var.public_subnet_2_cidr | ||
vpc_id = aws_vpc.production-vpc.id | ||
availability_zone = var.availability_zones[1] | ||
} | ||
|
||
# Private subnets | ||
resource "aws_subnet" "private-subnet-1" { | ||
cidr_block = var.private_subnet_1_cidr | ||
vpc_id = aws_vpc.production-vpc.id | ||
availability_zone = var.availability_zones[0] | ||
} | ||
resource "aws_subnet" "private-subnet-2" { | ||
cidr_block = var.private_subnet_2_cidr | ||
vpc_id = aws_vpc.production-vpc.id | ||
availability_zone = var.availability_zones[1] | ||
} | ||
|
||
# Route tables for the subnets | ||
resource "aws_route_table" "public-route-table" { | ||
vpc_id = aws_vpc.production-vpc.id | ||
} | ||
resource "aws_route_table" "private-route-table" { | ||
vpc_id = aws_vpc.production-vpc.id | ||
} | ||
|
||
# Associate the newly created route tables to the subnets | ||
resource "aws_route_table_association" "public-route-1-association" { | ||
route_table_id = aws_route_table.public-route-table.id | ||
subnet_id = aws_subnet.public-subnet-1.id | ||
} | ||
resource "aws_route_table_association" "public-route-2-association" { | ||
route_table_id = aws_route_table.public-route-table.id | ||
subnet_id = aws_subnet.public-subnet-2.id | ||
} | ||
resource "aws_route_table_association" "private-route-1-association" { | ||
route_table_id = aws_route_table.private-route-table.id | ||
subnet_id = aws_subnet.private-subnet-1.id | ||
} | ||
resource "aws_route_table_association" "private-route-2-association" { | ||
route_table_id = aws_route_table.private-route-table.id | ||
subnet_id = aws_subnet.private-subnet-2.id | ||
} | ||
|
||
# Elastic IP | ||
resource "aws_eip" "elastic-ip-for-nat-gw" { | ||
domain = "vpc" | ||
depends_on = [aws_internet_gateway.production-igw] | ||
} | ||
|
||
# NAT gateway | ||
resource "aws_nat_gateway" "nat-gw" { | ||
allocation_id = aws_eip.elastic-ip-for-nat-gw.id | ||
subnet_id = aws_subnet.public-subnet-1.id | ||
depends_on = [aws_eip.elastic-ip-for-nat-gw] | ||
} | ||
resource "aws_route" "nat-gw-route" { | ||
route_table_id = aws_route_table.private-route-table.id | ||
nat_gateway_id = aws_nat_gateway.nat-gw.id | ||
destination_cidr_block = "0.0.0.0/0" | ||
} | ||
|
||
# Internet Gateway for the public subnet | ||
resource "aws_internet_gateway" "production-igw" { | ||
vpc_id = aws_vpc.production-vpc.id | ||
} | ||
|
||
# Route the public subnet traffic through the Internet Gateway | ||
resource "aws_route" "public-internet-igw-route" { | ||
route_table_id = aws_route_table.public-route-table.id | ||
gateway_id = aws_internet_gateway.production-igw.id | ||
destination_cidr_block = "0.0.0.0/0" | ||
} | ||
|
||
|
84 changes: 84 additions & 0 deletions
84
{{cookiecutter.project_slug}}/terraform/03_securitygroups.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# ALB Security Group (Traffic Internet -> ALB) | ||
resource "aws_security_group" "load-balancer" { | ||
name = "load_balancer_security_group" | ||
description = "Controls access to the ALB" | ||
vpc_id = aws_vpc.production-vpc.id | ||
|
||
ingress { | ||
from_port = 80 | ||
to_port = 80 | ||
protocol = "tcp" | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
|
||
ingress { | ||
from_port = 443 | ||
to_port = 443 | ||
protocol = "tcp" | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
|
||
egress { | ||
from_port = 0 | ||
to_port = 0 | ||
protocol = "-1" | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
} | ||
|
||
# ECS Fargate Security group (traffic ALB -> ECS Fargate Tasks) | ||
resource "aws_security_group" "ecs-fargate" { | ||
name = "ecs_fargate_security_group" | ||
description = "Allows inbound access from the ALB only" | ||
vpc_id = aws_vpc.production-vpc.id | ||
|
||
ingress { | ||
from_port = 0 | ||
to_port = 0 | ||
protocol = "-1" | ||
security_groups = [aws_security_group.load-balancer.id] | ||
} | ||
|
||
# No SSH ingress rule since Fargate tasks are abstracted and not directly accessible via SSH | ||
|
||
egress { | ||
from_port = 0 | ||
to_port = 0 | ||
protocol = "-1" | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
} | ||
|
||
# RDS Security Group (traffic Fargate -> RDS) | ||
resource "aws_security_group" "rds" { | ||
name = "rds-security-group" | ||
description = "Allows inbound access from Fargate only" | ||
vpc_id = aws_vpc.production-vpc.id | ||
|
||
ingress { | ||
protocol = "tcp" | ||
from_port = "5432" | ||
to_port = "5432" | ||
security_groups = [aws_security_group.ecs-fargate.id] | ||
} | ||
|
||
egress { | ||
protocol = "-1" | ||
from_port = 0 | ||
to_port = 0 | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
} | ||
|
||
resource "aws_security_group" "efs_sg" { | ||
name = "EFS Security Group" | ||
description = "Allow ECS to EFS communication" | ||
vpc_id = aws_vpc.production-vpc.id | ||
|
||
ingress { | ||
from_port = 2049 # NFS port | ||
to_port = 2049 | ||
protocol = "tcp" | ||
cidr_blocks = ["0.0.0.0/0"] # Modify this based on your security requirements | ||
} | ||
} |
Oops, something went wrong.