soleprint init commit

This commit is contained in:
buenosairesam
2025-12-24 05:38:37 -03:00
commit 329c401ff5
96 changed files with 11564 additions and 0 deletions

View File

@@ -0,0 +1,341 @@
"""
AWS Infrastructure for Amar Mascotas
Deploys:
- VPC with public/private subnets
- EC2 instance for Django app + Celery
- RDS PostgreSQL (PostGIS via extension)
- ElastiCache Redis
- Security Groups
- (Optional) ALB, Route53
Estimated cost: ~$93/month
NOTE: AWS is more complex but offers more services and better scaling options.
"""
import pulumi
import pulumi_aws as aws
import sys
sys.path.append("..")
from shared.config import get_config, APP_SERVER_INIT_SCRIPT
# Load configuration
cfg = get_config()
# Get current region and availability zones
region = aws.get_region()
azs = aws.get_availability_zones(state="available")
az1 = azs.names[0]
az2 = azs.names[1] if len(azs.names) > 1 else azs.names[0]
# =============================================================================
# NETWORKING - VPC
# =============================================================================
vpc = aws.ec2.Vpc(
f"{cfg.resource_prefix}-vpc",
cidr_block="10.0.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-vpc"},
)
# Internet Gateway (for public internet access)
igw = aws.ec2.InternetGateway(
f"{cfg.resource_prefix}-igw",
vpc_id=vpc.id,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-igw"},
)
# Public subnets (for EC2, load balancer)
public_subnet_1 = aws.ec2.Subnet(
f"{cfg.resource_prefix}-public-1",
vpc_id=vpc.id,
cidr_block="10.0.1.0/24",
availability_zone=az1,
map_public_ip_on_launch=True,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-public-1"},
)
public_subnet_2 = aws.ec2.Subnet(
f"{cfg.resource_prefix}-public-2",
vpc_id=vpc.id,
cidr_block="10.0.2.0/24",
availability_zone=az2,
map_public_ip_on_launch=True,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-public-2"},
)
# Private subnets (for RDS, ElastiCache)
private_subnet_1 = aws.ec2.Subnet(
f"{cfg.resource_prefix}-private-1",
vpc_id=vpc.id,
cidr_block="10.0.10.0/24",
availability_zone=az1,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-private-1"},
)
private_subnet_2 = aws.ec2.Subnet(
f"{cfg.resource_prefix}-private-2",
vpc_id=vpc.id,
cidr_block="10.0.11.0/24",
availability_zone=az2,
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-private-2"},
)
# Route table for public subnets
public_rt = aws.ec2.RouteTable(
f"{cfg.resource_prefix}-public-rt",
vpc_id=vpc.id,
routes=[
aws.ec2.RouteTableRouteArgs(
cidr_block="0.0.0.0/0",
gateway_id=igw.id,
),
],
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-public-rt"},
)
# Associate route table with public subnets
aws.ec2.RouteTableAssociation(
f"{cfg.resource_prefix}-public-1-rta",
subnet_id=public_subnet_1.id,
route_table_id=public_rt.id,
)
aws.ec2.RouteTableAssociation(
f"{cfg.resource_prefix}-public-2-rta",
subnet_id=public_subnet_2.id,
route_table_id=public_rt.id,
)
# =============================================================================
# SECURITY GROUPS
# =============================================================================
# App server security group
app_sg = aws.ec2.SecurityGroup(
f"{cfg.resource_prefix}-app-sg",
vpc_id=vpc.id,
description="Security group for app server",
ingress=[
# SSH
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=22,
to_port=22,
cidr_blocks=cfg.allowed_ssh_ips or ["0.0.0.0/0"],
description="SSH access",
),
# HTTP
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=80,
to_port=80,
cidr_blocks=["0.0.0.0/0"],
description="HTTP",
),
# HTTPS
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=443,
to_port=443,
cidr_blocks=["0.0.0.0/0"],
description="HTTPS",
),
],
egress=[
aws.ec2.SecurityGroupEgressArgs(
protocol="-1",
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"],
description="Allow all outbound",
),
],
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-app-sg"},
)
# Database security group (only accessible from app server)
db_sg = aws.ec2.SecurityGroup(
f"{cfg.resource_prefix}-db-sg",
vpc_id=vpc.id,
description="Security group for RDS",
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=5432,
to_port=5432,
security_groups=[app_sg.id],
description="PostgreSQL from app",
),
],
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-db-sg"},
)
# Redis security group (only accessible from app server)
redis_sg = aws.ec2.SecurityGroup(
f"{cfg.resource_prefix}-redis-sg",
vpc_id=vpc.id,
description="Security group for ElastiCache",
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=6379,
to_port=6379,
security_groups=[app_sg.id],
description="Redis from app",
),
],
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-redis-sg"},
)
# =============================================================================
# DATABASE - RDS PostgreSQL
# =============================================================================
# Subnet group for RDS (requires at least 2 AZs)
db_subnet_group = aws.rds.SubnetGroup(
f"{cfg.resource_prefix}-db-subnet-group",
subnet_ids=[private_subnet_1.id, private_subnet_2.id],
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-db-subnet-group"},
)
# RDS PostgreSQL instance
# Note: PostGIS is available as an extension, enable after creation
db_instance = aws.rds.Instance(
f"{cfg.resource_prefix}-db",
identifier=f"{cfg.resource_prefix}-db",
engine="postgres",
engine_version=cfg.db_version,
instance_class="db.t3.micro", # $25/mo - smallest
allocated_storage=20,
storage_type="gp3",
db_name=cfg.db_name,
username=cfg.db_user,
password=pulumi.Config().require_secret("db_password"), # Set via: pulumi config set --secret db_password xxx
vpc_security_group_ids=[db_sg.id],
db_subnet_group_name=db_subnet_group.name,
publicly_accessible=False,
skip_final_snapshot=True, # Set False for production!
backup_retention_period=7,
multi_az=False, # Set True for HA ($$$)
tags=cfg.tags,
)
# =============================================================================
# CACHE - ElastiCache Redis
# =============================================================================
# Subnet group for ElastiCache
redis_subnet_group = aws.elasticache.SubnetGroup(
f"{cfg.resource_prefix}-redis-subnet-group",
subnet_ids=[private_subnet_1.id, private_subnet_2.id],
tags=cfg.tags,
)
# ElastiCache Redis cluster
redis_cluster = aws.elasticache.Cluster(
f"{cfg.resource_prefix}-redis",
cluster_id=f"{cfg.resource_prefix}-redis",
engine="redis",
engine_version="7.0",
node_type="cache.t3.micro", # $15/mo - smallest
num_cache_nodes=1,
port=6379,
subnet_group_name=redis_subnet_group.name,
security_group_ids=[redis_sg.id],
tags=cfg.tags,
)
# =============================================================================
# COMPUTE - EC2 Instance
# =============================================================================
# Get latest Ubuntu 22.04 AMI
ubuntu_ami = aws.ec2.get_ami(
most_recent=True,
owners=["099720109477"], # Canonical
filters=[
aws.ec2.GetAmiFilterArgs(
name="name",
values=["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"],
),
aws.ec2.GetAmiFilterArgs(
name="virtualization-type",
values=["hvm"],
),
],
)
# Key pair (import your existing key or create new)
# key_pair = aws.ec2.KeyPair(
# f"{cfg.resource_prefix}-key",
# public_key=open("~/.ssh/id_rsa.pub").read(),
# tags=cfg.tags,
# )
# EC2 instance
ec2_instance = aws.ec2.Instance(
f"{cfg.resource_prefix}-app",
ami=ubuntu_ami.id,
instance_type="t3.medium", # $35/mo - 4GB RAM, 2 vCPU
subnet_id=public_subnet_1.id,
vpc_security_group_ids=[app_sg.id],
# key_name=key_pair.key_name, # Uncomment when key_pair is defined
user_data=APP_SERVER_INIT_SCRIPT,
root_block_device=aws.ec2.InstanceRootBlockDeviceArgs(
volume_size=30,
volume_type="gp3",
),
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-app"},
)
# Elastic IP (static public IP)
eip = aws.ec2.Eip(
f"{cfg.resource_prefix}-eip",
instance=ec2_instance.id,
domain="vpc",
tags={**cfg.tags, "Name": f"{cfg.resource_prefix}-eip"},
)
# =============================================================================
# OPTIONAL: Application Load Balancer (uncomment if needed)
# =============================================================================
# alb = aws.lb.LoadBalancer(
# f"{cfg.resource_prefix}-alb",
# load_balancer_type="application",
# security_groups=[app_sg.id],
# subnets=[public_subnet_1.id, public_subnet_2.id],
# tags=cfg.tags,
# )
# =============================================================================
# OUTPUTS
# =============================================================================
pulumi.export("ec2_public_ip", eip.public_ip)
pulumi.export("ec2_private_ip", ec2_instance.private_ip)
pulumi.export("db_endpoint", db_instance.endpoint)
pulumi.export("db_name", cfg.db_name)
pulumi.export("db_user", cfg.db_user)
pulumi.export("redis_endpoint", redis_cluster.cache_nodes[0].address)
pulumi.export("redis_port", redis_cluster.port)
# Generate .env content
pulumi.export("env_file", pulumi.Output.all(
db_instance.endpoint,
redis_cluster.cache_nodes[0].address,
redis_cluster.port,
).apply(lambda args: f"""
# Generated by Pulumi - AWS
DB_HOST={args[0].split(':')[0]}
DB_PORT=5432
DB_NAME={cfg.db_name}
DB_USER={cfg.db_user}
DB_PASSWORD=<set via pulumi config>
CELERY_BROKER_URL=redis://{args[1]}:{args[2]}/0
CELERY_RESULT_BACKEND=redis://{args[1]}:{args[2]}/0
"""))