From cf369c6a3a3dc8a43d38af652000ce22fee28f9c Mon Sep 17 00:00:00 2001 From: Alexander Hosking Date: Thu, 7 Sep 2023 00:49:03 -0400 Subject: [PATCH] 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. --- air-tech/.gitignore | 2 + air-tech/Pulumi.prod.yaml | 2 + air-tech/Pulumi.yaml | 6 ++ air-tech/__main__.py | 216 ++++++++++++++++++++++++++++++++++++++ air-tech/requirements.txt | 2 + 5 files changed, 228 insertions(+) create mode 100644 air-tech/.gitignore create mode 100644 air-tech/Pulumi.prod.yaml create mode 100644 air-tech/Pulumi.yaml create mode 100644 air-tech/__main__.py create mode 100644 air-tech/requirements.txt 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