Mitch/aws py wordpress fargate rds
ADD
aws-py-wordpress-fargate-rds/.gitignore
+*.pyc
+venv/
+.vscode/

ADD
aws-py-wordpress-fargate-rds/Pulumi.yaml
+name: wordpress-ecs-rds
+runtime:
+ name: python
+ options:
+ virtualenv: venv
+description: Deploys WordPress in ECS Fargate with RDS backend.

ADD
aws-py-wordpress-fargate-rds/__main__.py
+"""
+Deploys:
+- Network: VPC, Subnets, Security Groups
+- DB Backend: MySQL RDS
+- FrontEnd: WordPress in Fargate
+"""
+
+import pulumi
+import pulumi_random as random
+import network
+import backend
+import frontend
+
+# Get config data
+config = pulumi.Config()
+service_name = config.get('service_name') or 'wp-example'
+db_name=config.get('db_name') or 'lampdb'
+db_user=config.get('db_user') or 'admin'
+
+# Get secretified password from config and protect it going forward, or create one using the 'random' provider.
+db_password=config.get_secret('db_password')
+if not db_password:
+ password=random.RandomPassword('db_password',
+ length=16,
+ special=True,
+ override_special='_%@',
+ )
+ # Pulumi knows this provider is used to create a password and thus automatically protects it going forward.
+ db_password=password.result
+
+# Create an AWS VPC and subnets, etc
+network=network.Vpc(f'{service_name}-net', network.VpcArgs())
+subnet_ids=[]
+for subnet in network.subnets:
+ subnet_ids.append(subnet.id)
+
+# Create a backend DB instance
+be=backend.Db(f'{service_name}-be', backend.DbArgs(
+ db_name=db_name,
+ db_user=db_user,
+ db_password=db_password,
+ # publicly_accessible=True, # Uncomment this to override for testing
+ subnet_ids=subnet_ids,
+ security_group_ids=[network.rds_security_group.id]
+))
+
+fe=frontend.WebService(f'{service_name}-fe', frontend.WebServiceArgs(
+ db_host=be.db.address,
+ db_port='3306',
+ db_name=be.db.name,
+ db_user=be.db.username,
+ db_password=be.db.password,
+ vpc_id=network.vpc.id,
+ subnet_ids=subnet_ids,
+ security_group_ids=[network.fe_security_group.id]
+))
+
+web_url=pulumi.Output.concat('http://', fe.alb.dns_name)
+pulumi.export('Web Service URL', web_url)
+pulumi.export('ECS Cluster Name', fe.cluster.name)
+
+pulumi.export('DB Endpoint', be.db.address)
+pulumi.export('DB User Name', be.db.username)
+pulumi.export('DB Password', be.db.password)

ADD
aws-py-wordpress-fargate-rds/backend.py
+from pulumi import ComponentResource, ResourceOptions
+from pulumi_aws import rds
+
+
+class DbArgs:
+
+ def __init__(self,
+ db_name=None,
+ db_user=None,
+ db_password=None,
+ subnet_ids=None,
+ security_group_ids=None,
+ allocated_storage=20,
+ engine='mysql',
+ engine_version='5.7',
+ instance_class='db.t2.micro',
+ storage_type='gp2',
+ skip_final_snapshot=True,
+ publicly_accessible=False):
+
+ self.db_name = db_name
+ self.db_user = db_user
+ self.db_password = db_password
+ self.subnet_ids = subnet_ids
+ self.security_group_ids = security_group_ids
+ self.allocated_storage = allocated_storage
+ self.engine = engine
+ self.engine_version = engine_version
+ self.instance_class = instance_class
+ self.storage_type = storage_type
+ self.skip_final_snapshot = skip_final_snapshot
+ self.publicly_accessible = publicly_accessible
+
+
+class Db(ComponentResource):
+
+ def __init__(self,
+ name: str,
+ args: DbArgs,
+ opts: ResourceOptions = None):
+
+ super().__init__('custom:resource:Backend', name, {}, opts)
+
+ # Create RDS subnet group to put RDS instance on.
+ subnet_group_name = f'{name}-sng'
+ rds_subnet_group = rds.SubnetGroup(subnet_group_name,
+ subnet_ids=args.subnet_ids,
+ tags={
+ 'Name': subnet_group_name
+ },
+ opts=ResourceOptions(parent=self)
+ )
+
+ rds_name = f'{name}-rds'
+ self.db = rds.Instance(rds_name,
+ name=args.db_name,
+ allocated_storage=args.allocated_storage,
+ engine=args.engine,
+ engine_version=args.engine_version,
+ instance_class=args.instance_class,
+ storage_type=args.storage_type,
+ db_subnet_group_name=rds_subnet_group.id,
+ username=args.db_user,
+ password=args.db_password,
+ vpc_security_group_ids=args.security_group_ids,
+ skip_final_snapshot=args.skip_final_snapshot,
+ publicly_accessible=args.publicly_accessible,
+ opts=ResourceOptions(parent=self)
+ )
+
+ self.register_outputs({})

ADD
aws-py-wordpress-fargate-rds/frontend.py
+from pulumi import ComponentResource, ResourceOptions, Output
+import pulumi_aws as aws
+import json
+
+
+class WebServiceArgs:
+
+ def __init__(self,
+ db_host=None,
+ db_port=None,
+ db_name=None,
+ db_user=None,
+ db_password=None,
+ vpc_id=None,
+ subnet_ids=None, # array of subnet IDs
+ security_group_ids=None): # array of security group Ids
+
+ self.db_host = db_host
+ self.db_port = db_port
+ self.db_name = db_name
+ self.db_user = db_user
+ self.db_password = db_password
+ self.vpc_id = vpc_id
+ self.subnet_ids = subnet_ids
+ self.security_group_ids = security_group_ids
+
+
+class WebService(ComponentResource):
+
+ def __init__(self,
+ name: str,
+ args: WebServiceArgs,
+ opts: ResourceOptions = None):
+
+ super().__init__('custom:resource:Frontend', name, {}, opts)
+
+ # Create an ECS cluster to run a container-based service.
+ self.cluster = aws.ecs.Cluster(f'{name}-ecs',
+ opts=ResourceOptions(parent=self)
+ )
+
+ # Create a load balancer to listen for HTTP traffic on port 80.
+ self.alb = aws.lb.LoadBalancer(f'{name}-alb',
+ security_groups=args.security_group_ids,
+ subnets=args.subnet_ids,
+ opts=ResourceOptions(parent=self)
+ )
+
+ atg = aws.lb.TargetGroup(f'{name}-app-tg',
+ port=80,
+ protocol='HTTP',
+ target_type='ip',
+ vpc_id=args.vpc_id,
+ health_check=aws.lb.TargetGroupHealthCheckArgs(
+ healthy_threshold=2,
+ interval=5,
+ timeout=4,
+ protocol='HTTP',
+ matcher='200-399'
+ ),
+ opts=ResourceOptions(parent=self)
+ )
+
+ wl = aws.lb.Listener(f'{name}-listener',
+ load_balancer_arn=self.alb.arn,
+ port=80,
+ default_actions=[aws.lb.ListenerDefaultActionArgs(
+ type='forward',
+ target_group_arn=atg.arn,
+ )],
+ opts=ResourceOptions(parent=self)
+ )
+
+ # Create an IAM role that can be used by our service's task.
+ role = aws.iam.Role(f'{name}-task-role',
+ assume_role_policy=json.dumps({
+ 'Version': '2008-10-17',
+ 'Statement': [{
+ 'Sid': '',
+ 'Effect': 'Allow',
+ 'Principal': {
+ 'Service': 'ecs-tasks.amazonaws.com'
+ },
+ 'Action': 'sts:AssumeRole',
+ }]
+ }),
+ opts=ResourceOptions(parent=self)
+ )
+
+ rpa = aws.iam.RolePolicyAttachment(f'{name}-task-policy',
+ role=role.name,
+ policy_arn='arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
+ opts=ResourceOptions(parent=self))
+
+ # Spin up a load balanced service running our container image.
+ task_name = f'{name}-app-task'
+ container_name = f'{name}-app-container'
+ self.task_definition = Output.all(args.db_host, args.db_port, args.db_name, args.db_user, args.db_password).apply(
+ lambda args:
+ aws.ecs.TaskDefinition(task_name,
+ family='fargate-task-definition',
+ cpu='256',
+ memory='512',
+ network_mode='awsvpc',
+ requires_compatibilities=[
+ 'FARGATE'],
+ execution_role_arn=role.arn,
+ container_definitions=json.dumps([{
+ 'name': container_name,
+ 'image': 'wordpress',
+ 'portMappings': [{
+ 'containerPort': 80,
+ 'hostPort': 80,
+ 'protocol': 'tcp'
+ }],
+ 'environment': [
+ {
+ 'name': 'WORDPRESS_DB_HOST',
+ 'value': f'{args[0]}:{args[1]}'
+ },
+ {
+ 'name': 'WORDPRESS_DB_NAME',
+ 'value': f'{args[2]}'
+ },
+ {
+ 'name': 'WORDPRESS_DB_USER',
+ 'value': f'{args[3]}'
+ },
+ {
+ 'name': 'WORDPRESS_DB_PASSWORD',
+ 'value': f'{args[4]}'
+ },
+ ]
+ }]),
+ opts=ResourceOptions(parent=self)
+ )
+ )
+
+ self.service = aws.ecs.Service(f'{name}-app-svc',
+ cluster=self.cluster.arn,
+ desired_count=1,
+ launch_type='FARGATE',
+ task_definition=self.task_definition.arn,
+ network_configuration=aws.ecs.ServiceNetworkConfigurationArgs(
+ assign_public_ip=True,
+ subnets=args.subnet_ids,
+ security_groups=args.security_group_ids
+ ),
+ load_balancers=[aws.ecs.ServiceLoadBalancerArgs(
+ target_group_arn=atg.arn,
+ container_name=container_name,
+ container_port=80,
+ )],
+ opts=ResourceOptions(
+ depends_on=[wl], parent=self),
+ )
+
+ self.register_outputs({})

ADD
aws-py-wordpress-fargate-rds/network.py
+from pulumi import ComponentResource, ResourceOptions
+from pulumi_aws import ec2, get_availability_zones
+
+# VPC
+
+
+class VpcArgs:
+
+ def __init__(self,
+ cidr_block='10.100.0.0/16',
+ instance_tenancy='default',
+ enable_dns_hostnames=True,
+ enable_dns_support=True,
+ ):
+ self.cidr_block = cidr_block
+ self.instance_tenancy = instance_tenancy
+ self.enable_dns_hostnames = enable_dns_hostnames
+ self.enable_dns_support = enable_dns_support
+
+
+class Vpc(ComponentResource):
+
+ def __init__(self,
+ name: str,
+ args: VpcArgs,
+ opts: ResourceOptions = None):
+
+ super().__init__('custom:resource:VPC', name, {}, opts)
+
+ vpc_name = name+'-vpc'
+ self.vpc = ec2.Vpc(vpc_name,
+ cidr_block=args.cidr_block,
+ instance_tenancy=args.instance_tenancy,
+ enable_dns_hostnames=args.enable_dns_hostnames,
+ enable_dns_support=args.enable_dns_support,
+ tags={
+ 'Name': vpc_name
+ },
+ opts=ResourceOptions(parent=self)
+ )
+
+ igw_name = name+'-igw'
+ self.igw = ec2.InternetGateway(igw_name,
+ vpc_id=self.vpc.id,
+ tags={
+ 'Name': igw_name
+ },
+ opts=ResourceOptions(parent=self)
+ )
+
+ rt_name = name+'-rt'
+ self.route_table = ec2.RouteTable(rt_name,
+ vpc_id=self.vpc.id,
+ routes=[ec2.RouteTableRouteArgs(
+ cidr_block='0.0.0.0/0',
+ gateway_id=self.igw.id,
+ )],
+ tags={
+ 'Name': rt_name
+ },
+ opts=ResourceOptions(parent=self)
+ )
+
+ # Subnets, at least across two zones.
+ all_zones = get_availability_zones()
+ # limiting to 2 zones for speed and to meet minimal requirements.
+ zone_names = [all_zones.names[0], all_zones.names[1]]
+ self.subnets = []
+ subnet_name_base = f'{name}-subnet'
+ for zone in zone_names:
+ vpc_subnet = ec2.Subnet(f'{subnet_name_base}-{zone}',
+ assign_ipv6_address_on_creation=False,
+ vpc_id=self.vpc.id,
+ map_public_ip_on_launch=True,
+ cidr_block=f'10.100.{len(self.subnets)}.0/24',
+ availability_zone=zone,
+ tags={
+ 'Name': f'{subnet_name_base}-{zone}',
+ },
+ opts=ResourceOptions(parent=self)
+ )
+ ec2.RouteTableAssociation(
+ f'vpc-route-table-assoc-{zone}',
+ route_table_id=self.route_table.id,
+ subnet_id=vpc_subnet.id,
+ opts=ResourceOptions(parent=self)
+ )
+ self.subnets.append(vpc_subnet)
+
+ # Security Groups
+ rds_sg_name = f'{name}-rds-sg'
+ self.rds_security_group = ec2.SecurityGroup(rds_sg_name,
+ vpc_id=self.vpc.id,
+ description='Allow client access.',
+ tags={
+ 'Name': rds_sg_name
+ },
+ ingress=[
+ ec2.SecurityGroupIngressArgs(
+ cidr_blocks=[
+ '0.0.0.0/0'],
+ from_port=3306,
+ to_port=3306,
+ protocol='tcp',
+ description='Allow rds access.'
+ ),
+ ],
+ egress=[
+ ec2.SecurityGroupEgressArgs(
+ protocol='-1',
+ from_port=0,
+ to_port=0,
+ cidr_blocks=[
+ '0.0.0.0/0'],
+ )],
+ opts=ResourceOptions(
+ parent=self)
+ )
+
+ fe_sg_name = f'{name}-fe-sg'
+ self.fe_security_group = ec2.SecurityGroup(fe_sg_name,
+ vpc_id=self.vpc.id,
+ description='Allow all HTTP(s) traffic.',
+ tags={
+ 'Name': fe_sg_name
+ },
+ ingress=[
+ ec2.SecurityGroupIngressArgs(
+ cidr_blocks=[
+ '0.0.0.0/0'],
+ from_port=443,
+ to_port=443,
+ protocol='tcp',
+ description='Allow https.'
+ ),
+ ec2.SecurityGroupIngressArgs(
+ cidr_blocks=[
+ '0.0.0.0/0'],
+ from_port=80,
+ to_port=80,
+ protocol='tcp',
+ description='Allow http access'
+ ),
+ ],
+ egress=[
+ ec2.SecurityGroupEgressArgs(
+ protocol='-1',
+ from_port=0,
+ to_port=0,
+ cidr_blocks=[
+ '0.0.0.0/0'],
+ )],
+ opts=ResourceOptions(
+ parent=self)
+ )
+
+ self.register_outputs({})

ADD
aws-py-wordpress-fargate-rds/requirements.txt
+pulumi>=2.0.0,<3.0.0
+pulumi-aws>=3.2.0,<4.0.0
+pulumi-random>=3.0.1,<4.0.0

Mitch/aws py wordpress fargate rds (#893)