commit cf369c6a3a3dc8a43d38af652000ce22fee28f9c Author: Alexander Hosking Date: Thu Sep 7 00:49:03 2023 -0400 This is nearly working in all Pulumi. Permissions are not correct in code to allow the images to be pulled by the ECS tasks. As such, they will not start and run without some manual intervention. Pulumi will happily let the process sit at this stage forever. diff --git a/air-tech/.gitignore b/air-tech/.gitignore new file mode 100644 index 0000000..a3807e5 --- /dev/null +++ b/air-tech/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/air-tech/Pulumi.prod.yaml b/air-tech/Pulumi.prod.yaml new file mode 100644 index 0000000..bdd6571 --- /dev/null +++ b/air-tech/Pulumi.prod.yaml @@ -0,0 +1,2 @@ +config: + aws:region: us-east-2 diff --git a/air-tech/Pulumi.yaml b/air-tech/Pulumi.yaml new file mode 100644 index 0000000..81e2656 --- /dev/null +++ b/air-tech/Pulumi.yaml @@ -0,0 +1,6 @@ +name: air-tech +runtime: + name: python + options: + virtualenv: venv +description: A technical assessment diff --git a/air-tech/__main__.py b/air-tech/__main__.py new file mode 100644 index 0000000..0460a41 --- /dev/null +++ b/air-tech/__main__.py @@ -0,0 +1,216 @@ +import pulumi +from pulumi import Config, Output, export +import pulumi_aws as aws +from pulumi_aws import acm, alb, ecs, ec2, lb, iam, ecr +import pulumi_awsx as awsx +import json + + +config = Config() +# container_port = config.get_int("containerPort", 80) +cpu = config.get_int("cpu", 512) +memory = config.get_int("memory", 128) +# Create a VPC +vpc = ec2.Vpc("production", cidr_block="10.0.0.0/16") + +# Create a Gateway +ig = ec2.InternetGateway("internet-gateway", vpc_id=vpc.id) + +cert = acm.Certificate( + "air-tech", + domain_name="air-tech.ahosking.com", + validation_method="DNS", +) +# Public Subnet +public_subnet = ec2.Subnet( + "public-subnet", + vpc_id=vpc.id, + cidr_block="10.0.1.0/26", + availability_zone="us-east-2a", + + # Allow the Internet Gateway to handle public traffic + map_public_ip_on_launch=True +) +public_subnet2 = ec2.Subnet( + "public-subnet2", + vpc_id=vpc.id, + cidr_block="10.0.2.0/26", + availability_zone="us-east-2b", + + # Allow the Internet Gateway to handle public traffic + map_public_ip_on_launch=True +) + +# Associate the Gateway to a route +route_table = ec2.RouteTable('route-table', vpc_id=vpc.id) +route = ec2.Route('route', route_table_id=route_table.id, + destination_cidr_block='0.0.0.0/0', gateway_id=ig.id) + +# Private Subnet +private_subnet = ec2.Subnet( + "private-subnet", + vpc_id=vpc.id, + cidr_block="10.0.3.0/26", + availability_zone="us-east-2a", + map_public_ip_on_launch=False +) +private_subnet2 = ec2.Subnet( + "private-subnet2", + vpc_id=vpc.id, + cidr_block="10.0.4.0/26", + availability_zone="us-east-2b", + map_public_ip_on_launch=False +) +repo = ecr.Repository("repo") + +# Create an IAM role +ecs_task_execution_role = iam.Role("ecsExecutionRole", + path="/", + assume_role_policy=iam.get_policy_document(statements=[{ + "actions": ["sts:AssumeRole"], + "principals": { + "type": "Service", + "identifiers": ["ecs-tasks.amazonaws.com"] + } + } + ]).json, + ) + +# Attach a policy to the execution role that will allow it to pull images from ECR +iam.RolePolicy("accessECRImages", + role=ecs_task_execution_role.id, + policy={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability", + ], + "Resource": repo.repository_arn + } + ] + } + ) + + +image_web = awsx.ecr.Image( + "image_web", + repository_url=repo.url, + path=".", + dockerfile=".\infra-web\Dockerfile") + +image_api = awsx.ecr.Image( + "image_api", + repository_url=repo.url, + path=".", + dockerfile=".\infra-api\Dockerfile") + +security_group = aws.ec2.SecurityGroup( + "securityGroup", + vpc_id=vpc.id, + egress=[aws.ec2.SecurityGroupEgressArgs( + from_port=0, + to_port=0, + protocol="-1", + cidr_blocks=["0.0.0.0/0"], + ipv6_cidr_blocks=["::/0"], + )]) +# Create an ECS Cluster +cluster = ecs.Cluster("cluster") + +pulumi.export("certificateArn", cert.arn) +loadbalancer = lb.LoadBalancer( + "loadbalancer", + load_balancer_type="application", + security_groups=[security_group.id], + subnets=[public_subnet.id, public_subnet2.id]) + + +target_group = lb.TargetGroup('target-group', + port=5000, + protocol="HTTP", + target_type="ip", + vpc_id=vpc.id) + +# Create a listener for the ALB at port 80 +listener = lb.Listener('listener', + load_balancer_arn=loadbalancer.arn, + port=80, + default_actions=[{ + 'type': "forward", + 'target_group_arn': target_group.arn # Forward to our target group + }]) + + +web_sg = ec2.SecurityGroup('web-sg', + vpc_id=vpc.id, + ingress=[ + { + 'protocol': 'tcp', + 'from_port': 80, + 'to_port': 80, + 'cidr_blocks': ['0.0.0.0/0'], + } + ], + egress=[ + { + 'protocol': '-1', + 'from_port': 0, + 'to_port': 0, + 'cidr_blocks': ['0.0.0.0/0'], + } + ] + ) + + +api = awsx.ecs.FargateService( + "api", + cluster=cluster.arn, + network_configuration={ + 'assignPublicIp': "false", + 'subnets': [private_subnet.id], + 'securityGroups': [web_sg.id] + }, + task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( + container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( + name="theApi", + image=image_api.image_uri, + cpu=cpu, + memory=memory, + essential=True, + port_mappings=[awsx.ecs.TaskDefinitionPortMappingArgs( + container_port=5000, + host_port=5000, + )], + ), + )) + + +service = awsx.ecs.FargateService( + "web", + cluster=cluster.arn, + network_configuration={ + 'assignPublicIp': "true", + 'subnets': [public_subnet.id], + 'securityGroups': [web_sg.id] + }, + # assign_public_ip=True, + task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( + container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( + name="theOne", + image=image_web.image_uri, + cpu=cpu, + memory=memory, + essential=True, + port_mappings=[awsx.ecs.TaskDefinitionPortMappingArgs( + container_port=5000, + host_port=5000, + target_group=target_group, + )], + ), + ), + opts=pulumi.ResourceOptions(depends_on=[api, target_group]) +) diff --git a/air-tech/requirements.txt b/air-tech/requirements.txt new file mode 100644 index 0000000..72aee79 --- /dev/null +++ b/air-tech/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-aws>=6.0.2,<7.0.0