From 1fc5da5634244b0710ba183afdeb0e15d21223d1 Mon Sep 17 00:00:00 2001 From: rumitparmar Date: Mon, 13 Nov 2023 14:23:11 +0530 Subject: [PATCH 1/2] commit for new branch --- Bookstore-Infra/.terraform.lock.hcl | 41 ++++++++++++++++++++++++++++ Bookstore-Infra/secret_password.json | 4 +++ 2 files changed, 45 insertions(+) create mode 100644 Bookstore-Infra/.terraform.lock.hcl create mode 100644 Bookstore-Infra/secret_password.json diff --git a/Bookstore-Infra/.terraform.lock.hcl b/Bookstore-Infra/.terraform.lock.hcl new file mode 100644 index 0000000..896c545 --- /dev/null +++ b/Bookstore-Infra/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.25.0" + hashes = [ + "h1:NkAtnv7ueO1PbcZRWxDsfgHGmzxG7gUuTbMgCe4l9zw=", + "zh:163244f73f13b013d9a6a267731df5971d03a3cdcdefb6d280dcbb39e482d76b", + "zh:179debe1fcfff552589c949e068ff7cae5f4be6b00b48f20933029795210f4c1", + "zh:1c66da7cd54614c57d3f7fa0f5f2b51864358250d360cdb6c403f4248ab15fd4", + "zh:360cdc430c4e79a7b9b791832460e9caccafd9dd1d9bb9a95293aaebce5d506c", + "zh:40eb5dd1e678528fb881b448624c68f10f1878bfbbf4160c829bb2255f8ae159", + "zh:76a30a8a5cc9132202929c4ec5dd4f1ac4089af73a0d417a965cd23801e69526", + "zh:7fbd7796e787635640ba5b489b6b1cd9294156e7f222f70f0b9672d0302bbf67", + "zh:8408832a7540e758397e55dba2134b7428b7318d515f7226dd548231bc61e3df", + "zh:97db8d60e240701b7010eff29e3d44360856b7f3e72a217d1a57d6315bbb8e08", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f9c7b9203ac445b65ca8f3cfa176d9e86dc5572ce925490d7fabb04ebf4f829", + "zh:ca5c18799ab858a1addcbfb10cf227e013eb12c110b7d750c969fc8711ff578b", + "zh:cdd503da9f461ad5bc119cb3003be85b3752457b552f41ed20315e1239e3084a", + "zh:f8aaad1544823803dfd03e22ed260e706524baf5509cb1c4cf2be9d982c0fa6c", + "zh:ff283a7aaf509b3a9ba17a2efbc2faccbc69d84e913b23f2434dd5102becac3c", + ] +} + +provider "registry.terraform.io/hashicorp/template" { + version = "2.2.0" + hashes = [ + "h1:N2IXrfDhxid5pFKHISN9V23ptHJyFZEdojUegdx5YkM=", + "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", + "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", + "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603", + "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16", + "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776", + "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451", + "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae", + "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde", + "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d", + "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", + ] +} diff --git a/Bookstore-Infra/secret_password.json b/Bookstore-Infra/secret_password.json new file mode 100644 index 0000000..ae76ede --- /dev/null +++ b/Bookstore-Infra/secret_password.json @@ -0,0 +1,4 @@ +{ + "password": "AdminBookstore@001" + } + \ No newline at end of file From bc7e3882cb39b5c1fbf356cd259ac50f8d315716 Mon Sep 17 00:00:00 2001 From: rumitparmar Date: Mon, 13 Nov 2023 14:57:05 +0530 Subject: [PATCH 2/2] Include Project-work-Documentation file --- .github/workflows/project.yml | 133 +++++++++++++++++++++ Bookstore-Infra/.gitignore | 39 +++++++ Bookstore-Infra/ACM.tf | 85 ++++++++++++++ Bookstore-Infra/README.md | 1 + Bookstore-Infra/asg_alb.tf | 151 ++++++++++++++++++++++++ Bookstore-Infra/bookstore | 38 ++++++ Bookstore-Infra/bookstore.pub | 1 + Bookstore-Infra/ecr.tf | 8 ++ Bookstore-Infra/ecs.tf | 168 +++++++++++++++++++++++++++ Bookstore-Infra/output.tf | 7 ++ Bookstore-Infra/provider.tf | 14 +++ Bookstore-Infra/rds.tf | 24 ++++ Bookstore-Infra/role.tf | 25 ++++ Bookstore-Infra/secret_username.json | 4 + Bookstore-Infra/secrete.tf | 64 ++++++++++ Bookstore-Infra/security_group.tf | 46 ++++++++ Bookstore-Infra/variable.tf | 111 ++++++++++++++++++ Bookstore-Infra/vpc.tf | 98 ++++++++++++++++ Project-work-Documentation.docx | Bin 0 -> 13812 bytes 19 files changed, 1017 insertions(+) create mode 100644 .github/workflows/project.yml create mode 100644 Bookstore-Infra/.gitignore create mode 100644 Bookstore-Infra/ACM.tf create mode 100644 Bookstore-Infra/README.md create mode 100644 Bookstore-Infra/asg_alb.tf create mode 100644 Bookstore-Infra/bookstore create mode 100644 Bookstore-Infra/bookstore.pub create mode 100644 Bookstore-Infra/ecr.tf create mode 100644 Bookstore-Infra/ecs.tf create mode 100644 Bookstore-Infra/output.tf create mode 100644 Bookstore-Infra/provider.tf create mode 100644 Bookstore-Infra/rds.tf create mode 100644 Bookstore-Infra/role.tf create mode 100644 Bookstore-Infra/secret_username.json create mode 100644 Bookstore-Infra/secrete.tf create mode 100644 Bookstore-Infra/security_group.tf create mode 100644 Bookstore-Infra/variable.tf create mode 100644 Bookstore-Infra/vpc.tf create mode 100644 Project-work-Documentation.docx diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml new file mode 100644 index 0000000..31f532b --- /dev/null +++ b/.github/workflows/project.yml @@ -0,0 +1,133 @@ +name: .NETProject + +on: + push: + branches: [ "main" ] + + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal + + - name: Check if Docker Compose is installed + run: | + if ! command -v docker-compose &> /dev/null + then + echo "Docker Compose is not installed. Installing..." + sudo apt-get update + sudo apt-get install -y docker-compose + else + echo "Docker Compose is already installed." + fi + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Docker + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS CLI + run: | + aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} + aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws configure set region ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + with: + registry: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com + + - name: Build and push Docker image -Migrator + run: | + docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.MIGRATOR }}:latest --target migrator . + docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.MIGRATOR }}:latest + + - name: Build and push Docker image - WEB + run: | + docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.WEB }}:latest --target web . + docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.WEB }}:latest + + + + - name: Build and push Docker image -Auth + run: | + docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AUTH }}:latest --target auth . + docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AUTH }}:latest + + + + - name: Build and push Docker image - App + run: | + docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.HOST }}:latest --target host . + docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.HOST }}:latest + + + - name: Update ECS service + run: | + aws ecs update-service --cluster bookstore-cluster --service app --force-new-deployment --debug + + + + - name: Update ECS service + run: | + aws ecs update-service --cluster bookstore-cluster --service migrator --force-new-deployment --debug + + + - name: Update ECS service + run: | + aws ecs update-service --cluster bookstore-cluster --service auth --force-new-deployment --debug + + + - name: Update ECS service + run: | + aws ecs update-service --cluster bookstore-cluster --service web --force-new-deployment --debug + + + + # - name: Build and push Docker image - DB + # run: | + # docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.DB }}:latest --target db . + # docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.DB }}:latest + + + + # - name: Update ECS service + # run: | + # aws ecs update-service --cluster ${{ secrets.AWS_ECS_CLUSTER }} --service ${{ secrets.HOST }} --force-new-deployment + + + # - name: Update ECS service + # run: | + # aws ecs update-service --cluster ${{ secrets.AWS_ECS_CLUSTER }} --service ${{ secrets.AUTH }} --force-new-deployment + + + # - name: Update ECS service + # run: | + # aws ecs update-service --cluster ${{ secrets.AWS_ECS_CLUSTER }} --service ${{ secrets.WEB }} --force-new-deployment + + + + # - name: Update ECS service + # run: | + # aws ecs update-service --cluster ${{ secrets.AWS_ECS_CLUSTER }} --service ${{ secrets.MIGRATOR }} --force-new-deployment + + + + + + + diff --git a/Bookstore-Infra/.gitignore b/Bookstore-Infra/.gitignore new file mode 100644 index 0000000..8715e83 --- /dev/null +++ b/Bookstore-Infra/.gitignore @@ -0,0 +1,39 @@ +secret.tf +secret_password.json +secret.username.json +.terraform.lock.hcl + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc \ No newline at end of file diff --git a/Bookstore-Infra/ACM.tf b/Bookstore-Infra/ACM.tf new file mode 100644 index 0000000..21bc5d9 --- /dev/null +++ b/Bookstore-Infra/ACM.tf @@ -0,0 +1,85 @@ + # # Variables + # variable "domain_name" { + # description = "bookstore-Public-ALB-2112943775.us-east-1.elb.amazonaws.com" + # } + + # # ACM Certificate + # resource "aws_acm_certificate" "ssl_certificate" { + # domain_name = var.domain_name + # validation_method = "DNS" + + # lifecycle { + # create_before_destroy = true + # } + # } + + # # Route 53 Hosted Zone + # resource "aws_route53_zone" "my_zone" { + # name = var.domain_name + # } + + # # ACM Certificate DNS Validation Record + # resource "aws_route53_record" "acm_validation" { + # for_each = aws_acm_certificate.ssl_certificate.domain_validation_options + + # name = each.value.resource_record_name + # type = each.value.resource_record_type + # records = [each.value.resource_record_value] + # zone_id = aws_route53_zone.my_zone.zone_id + # ttl = 60 + # } + + + +# ############################ Validation using EMAIL ############################################################################################################# + + + +# # ACM Certificate +# resource "aws_acm_certificate" "ssl_certificate" { +# domain_name = "bookstore-Public-ALB-2112943775.us-east-1.elb.amazonaws.com" +# validation_method = "EMAIL" + +# lifecycle { +# create_before_destroy = true +# } +# } + +# # Wait for ACM Certificate Validation (Email) +# resource "aws_acm_certificate_validation" "ssl_certificate_validation" { +# certificate_arn = aws_acm_certificate.ssl_certificate.arn +# } + +# # ALB Listener Rule for HTTPS +# resource "aws_lb_listener" "alb_listener_https" { +# load_balancer_arn = aws_lb.alb.arn +# port = 443 +# protocol = "HTTPS" +# ssl_policy = "ELBSecurityPolicy-2016-08" +# certificate_arn = aws_acm_certificate_validation.ssl_certificate_validation.certificate_arn + +# default_action { +# type = "forward" +# target_group_arn = aws_lb_target_group.lb_target_group.arn +# } +# } + + +# resource "aws_acm_certificate" "ssl_certificate" { +# domain_name = "bookstore-Public-ALB-2112943775.us-east-1.elb.amazonaws.com" +# validation_method = "DNS" +# } + +# resource "aws_acm_certificate_validation" "ssl_certificate_validation" { +# certificate_arn = aws_acm_certificate.ssl_certificate.arn +# validation_record_fqdns = aws_acm_certificate.ssl_certificate.domain_validation_options.*.resource_record_name +# } + +# # Ensure ACM certificate validation completes before creating other resources +# resource "null_resource" "wait_for_acm_validation" { +# depends_on = [aws_acm_certificate_validation.ssl_certificate_validation] + +# provisioner "local-exec" { +# command = "echo ACM certificate has been successfully validated!" +# } +# } diff --git a/Bookstore-Infra/README.md b/Bookstore-Infra/README.md new file mode 100644 index 0000000..fda01be --- /dev/null +++ b/Bookstore-Infra/README.md @@ -0,0 +1 @@ +# Bookstore-Infra \ No newline at end of file diff --git a/Bookstore-Infra/asg_alb.tf b/Bookstore-Infra/asg_alb.tf new file mode 100644 index 0000000..b887adb --- /dev/null +++ b/Bookstore-Infra/asg_alb.tf @@ -0,0 +1,151 @@ + resource "aws_launch_configuration" "ecs_launch" { + name = "${var.project}-Launch-config" + image_id = var.AMI + instance_type = var.asg_instance_type + key_name = var.key_name + user_data = "#!/bin/bash \n echo ECS_CLUSTER=${aws_ecs_cluster.ecs_cluster.name} >> /etc/ecs/ecs.config" + #user_data = base64encode(var.lg_user_data) + + security_groups = [aws_security_group.ecs_sg.id] + + iam_instance_profile = aws_iam_instance_profile.ecs_agent.name +} + + +resource "aws_autoscaling_group" "ecs_asg" { + name = "${var.project}-ASG" + vpc_zone_identifier = [for subnet in aws_subnet.public_subnets[*] : subnet.id] + launch_configuration = aws_launch_configuration.ecs_launch.name + desired_capacity = var.desired_capacity + min_size = var.min_size + max_size = var.max_size + health_check_grace_period = var.health_check_grace_period + health_check_type = "EC2" +} + +resource "aws_lb" "alb" { + name = "${var.project}-Public-ALB" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.ecs_sg.id] + subnets = [for subnet in aws_subnet.public_subnets : subnet.id] + tags = { + Createdwith = "Terraform" + } +} + +resource "aws_lb_target_group" "lb_target_group" { + name = "${var.project}-Target-Group" + port = 80 + protocol = "HTTP" + vpc_id = aws_vpc.main.id + target_type = "instance" + tags = { + Createdwith = "Terraform" + } +} + + +resource "aws_autoscaling_attachment" "asg_to_target_group" { + autoscaling_group_name = aws_autoscaling_group.ecs_asg.name + lb_target_group_arn = aws_lb_target_group.lb_target_group.arn +} + +resource "aws_lb_listener" "alb_listener" { + load_balancer_arn = aws_lb.alb.arn + port = "80" + protocol = "HTTP" + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.lb_target_group.arn + } +} +# resource "aws_lb_listener" "alb_listener_https" { +# load_balancer_arn = aws_lb.alb.arn +# port = 443 +# protocol = "HTTPS" +# ssl_policy = "ELBSecurityPolicy-2016-08" +# certificate_arn = aws_acm_certificate.ssl_certificate.arn + +# default_action { +# type = "forward" +# target_group_arn = aws_lb_target_group.lb_target_group.arn +# } +# } + + + + +#scale up policy +resource "aws_autoscaling_policy" "scale_up" { + name = "${var.project}-asg-scale-up" + autoscaling_group_name = aws_autoscaling_group.ecs_asg.name + adjustment_type = "ChangeInCapacity" + scaling_adjustment = "1" #increasing instance by 1 + cooldown = "300" + policy_type = "SimpleScaling" +} + +# scale up alarm +resource "aws_cloudwatch_metric_alarm" "scale_up_alarm" { + alarm_name = "${var.project}-asg-scale-up-alarm" + alarm_description = "asg-scale-up-cpu-alarm" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "5" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "60" + statistic = "Average" + threshold = "60" + dimensions = { + "AutoScalingGroupName" = aws_autoscaling_group.ecs_asg.name + } + actions_enabled = true + alarm_actions = [aws_autoscaling_policy.scale_up.arn] +} + +# Alarm for average CPU Utilization og greater than 60% for constant 20 minutes +resource "aws_cloudwatch_metric_alarm" "constant_cpu_60_percent_up_alarm" { + alarm_name = "${var.project}-constant_cpu_60_percent_up_alarm" + alarm_description = "asg-scale-up-cpu-alarm" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "20" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "60" + statistic = "Average" + threshold = "60" + dimensions = { + "AutoScalingGroupName" = aws_autoscaling_group.ecs_asg.name + } + actions_enabled = true + alarm_actions = [aws_autoscaling_policy.scale_up.arn] +} + +# scale down policy +resource "aws_autoscaling_policy" "scale_down" { + name = "${var.project}-asg-scale-down" + autoscaling_group_name = aws_autoscaling_group.ecs_asg.name + adjustment_type = "ChangeInCapacity" + scaling_adjustment = "-1" # decreasing instance by 1 + cooldown = "300" + policy_type = "SimpleScaling" +} + +# scale down alarm +resource "aws_cloudwatch_metric_alarm" "scale_down_alarm" { + alarm_name = "${var.project}-asg-scale-down-alarm" + alarm_description = "asg-scale-down-cpu-alarm" + comparison_operator = "LessThanOrEqualToThreshold" + evaluation_periods = "20" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "60" + statistic = "Average" + threshold = "40" + dimensions = { + "AutoScalingGroupName" = aws_autoscaling_group.ecs_asg.name + } + actions_enabled = true + alarm_actions = [aws_autoscaling_policy.scale_down.arn] +} diff --git a/Bookstore-Infra/bookstore b/Bookstore-Infra/bookstore new file mode 100644 index 0000000..b4e783c --- /dev/null +++ b/Bookstore-Infra/bookstore @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/Bookstore-Infra/bookstore.pub b/Bookstore-Infra/bookstore.pub new file mode 100644 index 0000000..76b55dd --- /dev/null +++ b/Bookstore-Infra/bookstore.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCVmdK0kXX1LGpROGFC9tK3ZNxXrDBHsSV7rSZEtr4cL92E4Nbk5736IXbO3UQ5sxPYcp7XIVY6nMME1fm1qWID462jdkhJZRePOASJuPxnznVpEldDRpeA7RupCpZdHC1XKy2HyYo9OAII8IGoPwHKd///uy0TaLkL3bBceomqcW9qffhUpVsOCojGU31QjbE3Rvs0oWgorOzP19+997nodTfVCCxooy0Yp0R7NCT5zCtLgkYKI4xH66o+/H6KQVsXGwN6bANphyGy3vahFzocd0lF9iEHyT79zbnExpUR/TELdYfr1qgKjrAHMXo49tFamPyWy0w+i2ndiLgz8C87sgfVZOPf/Lmzr689w58/RQBOwNIBDt5CKhPOIz1sIIO8KyHSHST72FwBDSh9aMA2mfIorLQdLV1lxoWMK8wlDEZZDZ9LgdAv5zdhjqPwpaQ/y+JPCXja4JYrAdQsKsa8IX5Dtgl40c4pFpjUuzEPugsPve91HtUKc71JPrwqlJ0= admin@DESKTOP-GAU9HFH diff --git a/Bookstore-Infra/ecr.tf b/Bookstore-Infra/ecr.tf new file mode 100644 index 0000000..efedc29 --- /dev/null +++ b/Bookstore-Infra/ecr.tf @@ -0,0 +1,8 @@ +resource "aws_ecr_repository" "ecr_repo" { + count = length(var.repository_names) + name = var.repository_names[count.index] + + image_scanning_configuration { + scan_on_push = true + } +} \ No newline at end of file diff --git a/Bookstore-Infra/ecs.tf b/Bookstore-Infra/ecs.tf new file mode 100644 index 0000000..5de4323 --- /dev/null +++ b/Bookstore-Infra/ecs.tf @@ -0,0 +1,168 @@ +resource "aws_ecs_cluster" "ecs_cluster" { + name = "${var.project}-cluster" +} + +####################### ECS task for app ####################### + +resource "aws_ecs_task_definition" "app_task_definition" { + family = "app" + requires_compatibilities = ["EC2"] + container_definitions = jsonencode([ + { + "name" : "app-container", + "image" : "app:latest", + "portMappings" : [ + { + "containerPort" : 80, + "hostPort" : 80 + } + ], + "memory" : 512, + "memoryReservation" : 256, + "dockerLabels" : { + "image" : "nginx" + } + }, + ]) +} + +resource "aws_ecs_service" "app" { + name = "app" + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.app_task_definition.arn + desired_count = 1 +} + + +####################### ECS task for auth ####################### + + + +resource "aws_ecs_task_definition" "auth_task_definition" { + family = "auth" + requires_compatibilities = ["EC2"] + container_definitions = jsonencode([ + { + "name" : "auth-container", + "image" : "auth:latest", + "portMappings" : [ + { + "containerPort" : 80, + "hostPort" : 80 + } + ], + "memory" : 512, + "memoryReservation" : 256, + "dockerLabels" : { + "image" : "nginx" + } + }, + ]) +} + +resource "aws_ecs_service" "auth" { + name = "auth" + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.auth_task_definition.arn + desired_count = 1 +} + + + +####################### ECS task for db ####################### + + + +# resource "aws_ecs_task_definition" "db_task_definition" { +# family = "db" +# requires_compatibilities = ["EC2"] +# container_definitions = jsonencode([ +# { +# "name" : "db", +# "image" : "db:latest", +# "portMappings" : [ +# { +# "containerPort" : 80, +# "hostPort" : 80 +# } +# ], +# "memory" : 512, +# "memoryReservation" : 256, +# "dockerLabels" : { +# "image" : "nginx" +# } +# }, +# ]) +# } +# resource "aws_ecs_service" "db" { +# name = "db" +# cluster = aws_ecs_cluster.ecs_cluster.id +# task_definition = aws_ecs_task_definition.db_task_definition.arn +# desired_count = 1 +# } + + +####################### ECS task for migrator ####################### + + + +resource "aws_ecs_task_definition" "migrator_task_definition" { + family = "migrator" + requires_compatibilities = ["EC2"] + container_definitions = jsonencode([ + { + "name" : "migrator", + "image" : "migrator:latest", + "portMappings" : [ + { + "containerPort" : 80, + "hostPort" : 80 + } + ], + "memory" : 512, + "memoryReservation" : 256, + "dockerLabels" : { + "image" : "nginx" + } + }, + ]) +} + +resource "aws_ecs_service" "migrator" { + name = "migrator" + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.migrator_task_definition.arn + desired_count = 1 +} + + +####################### ECS task for web ####################### + +resource "aws_ecs_task_definition" "web_task_definition" { + family = "web" + requires_compatibilities = ["EC2"] + container_definitions = jsonencode([ + { + "name" : "web", + "image" : "web:latest", + "portMappings" : [ + { + "containerPort" : 80, + "hostPort" : 80 + } + ], + "memory" : 512, + "memoryReservation" : 256, + "dockerLabels" : { + "image" : "nginx" + } + }, + ]) +} + +resource "aws_ecs_service" "web" { + name = "web" + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.web_task_definition.arn + desired_count = 1 +} \ No newline at end of file diff --git a/Bookstore-Infra/output.tf b/Bookstore-Infra/output.tf new file mode 100644 index 0000000..dbc1f97 --- /dev/null +++ b/Bookstore-Infra/output.tf @@ -0,0 +1,7 @@ +output "EIP" { + value = aws_eip.nat_eip.address +} + +# output "launch_configuration_name" { +# value = aws_launch_configuration.ecs_launch_config.name +# } diff --git a/Bookstore-Infra/provider.tf b/Bookstore-Infra/provider.tf new file mode 100644 index 0000000..92f28e8 --- /dev/null +++ b/Bookstore-Infra/provider.tf @@ -0,0 +1,14 @@ +provider "aws" { + region = var.region +} + + +terraform { + backend "s3" { + + bucket = "main-project-bucket" + key = "bookstore/terraform.tfstate" + region = "us-east-1" + encrypt = true + } +} \ No newline at end of file diff --git a/Bookstore-Infra/rds.tf b/Bookstore-Infra/rds.tf new file mode 100644 index 0000000..e294eea --- /dev/null +++ b/Bookstore-Infra/rds.tf @@ -0,0 +1,24 @@ +resource "aws_db_subnet_group" "db_subnet_group" { + subnet_ids = [for subnet in aws_subnet.private_subnets : subnet.id] +} +resource "aws_db_instance" "postgres" { + identifier = "postgres" + allocated_storage = 7 + backup_retention_period = 2 + backup_window = "01:00-01:30" + maintenance_window = "sun:03:00-sun:03:30" + engine = "postgres" + engine_version = "14.7" + instance_class = var.db_instance_class + username = aws_secretsmanager_secret.unique_username_secret.name + password = aws_secretsmanager_secret.unique_password_secret.name + + # username = aws_secretsmanager_secret_version.db_username_version_new1.arn + # password = aws_secretsmanager_secret_version.db_password_version_new1.arn + port = "5432" + db_subnet_group_name = aws_db_subnet_group.db_subnet_group.id + vpc_security_group_ids = [aws_security_group.rds_sg.id, aws_security_group.ecs_sg.id] + skip_final_snapshot = true + final_snapshot_identifier = "worker-final" + publicly_accessible = false +} diff --git a/Bookstore-Infra/role.tf b/Bookstore-Infra/role.tf new file mode 100644 index 0000000..04a1fd1 --- /dev/null +++ b/Bookstore-Infra/role.tf @@ -0,0 +1,25 @@ +data "aws_iam_policy_document" "ecs_agent" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "ecs_agent" { + name = "ecs-agent" + assume_role_policy = data.aws_iam_policy_document.ecs_agent.json +} + +resource "aws_iam_instance_profile" "ecs_agent" { + name = "ecs-agent" + role = aws_iam_role.ecs_agent.name +} + +resource "aws_iam_role_policy_attachment" "ecs_agent" { + role = aws_iam_role.ecs_agent.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" +} \ No newline at end of file diff --git a/Bookstore-Infra/secret_username.json b/Bookstore-Infra/secret_username.json new file mode 100644 index 0000000..3c5f3a8 --- /dev/null +++ b/Bookstore-Infra/secret_username.json @@ -0,0 +1,4 @@ +{ + "username": "admin" + } + \ No newline at end of file diff --git a/Bookstore-Infra/secrete.tf b/Bookstore-Infra/secrete.tf new file mode 100644 index 0000000..1dd77b1 --- /dev/null +++ b/Bookstore-Infra/secrete.tf @@ -0,0 +1,64 @@ +# # Define the Secrets Manager secret for username +# resource "aws_secretsmanager_secret" "db_username_secret_with_new_name" { +# name = "TopSecretUsernameSecret" # Change the name +# } + +# # Load the template file for username +# data "template_file" "username" { +# template = file("secret_username.json") +# } + +# # Create the secret version for username +# resource "aws_secretsmanager_secret_version" "db_username_version_with_new_name" { +# secret_id = aws_secretsmanager_secret.db_username_secret_with_new_name.id +# secret_string = data.template_file.username.rendered +# } + +# # Define the Secrets Manager secret for password +# resource "aws_secretsmanager_secret" "db_password_secret_with_new_name" { +# name = "ClassifiedPasswordSecret" # Change the name +# } + +# # Load the template file for password +# data "template_file" "password" { +# template = file("secret_password.json") +# } + +# # Create the secret version for password +# resource "aws_secretsmanager_secret_version" "db_password_version_with_new_name" { +# secret_id = aws_secretsmanager_secret.db_password_secret_with_new_name.id +# secret_string = data.template_file.password.rendered +# } + + +# Define the Secrets Manager secret for username +resource "aws_secretsmanager_secret" "unique_username_secret" { + name = "TopSecretUsernameSecret" +} + +# Load the template file for username +data "template_file" "username" { + template = file("secret_username.json") +} + +# Create the secret version for username +resource "aws_secretsmanager_secret_version" "unique_username_version" { + secret_id = aws_secretsmanager_secret.unique_username_secret.id + secret_string = data.template_file.username.rendered +} + +# Define the Secrets Manager secret for password +resource "aws_secretsmanager_secret" "unique_password_secret" { + name = "ClassifiedPasswordSecret" +} + +# Load the template file for password +data "template_file" "password" { + template = file("secret_password.json") +} + +# Create the secret version for password +resource "aws_secretsmanager_secret_version" "unique_password_version" { + secret_id = aws_secretsmanager_secret.unique_password_secret.id + secret_string = data.template_file.password.rendered +} diff --git a/Bookstore-Infra/security_group.tf b/Bookstore-Infra/security_group.tf new file mode 100644 index 0000000..dc86d97 --- /dev/null +++ b/Bookstore-Infra/security_group.tf @@ -0,0 +1,46 @@ +resource "aws_security_group" "ecs_sg" { + vpc_id = aws_vpc.main.id + name = "VPC_SG" + + + dynamic "ingress" { + for_each = [22, 80, 443, 3306, 5432] + iterator = port + content { + description = "sg" + from_port = port.value + to_port = port.value + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + } + + dynamic "egress" { + for_each = [1] + content { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } +} + +resource "aws_security_group" "rds_sg" { + vpc_id = aws_vpc.main.id + + ingress { + protocol = "tcp" + from_port = 5432 + to_port = 5432 + cidr_blocks = ["0.0.0.0/0"] + security_groups = [aws_security_group.ecs_sg.id] + } + + egress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} \ No newline at end of file diff --git a/Bookstore-Infra/variable.tf b/Bookstore-Infra/variable.tf new file mode 100644 index 0000000..2547675 --- /dev/null +++ b/Bookstore-Infra/variable.tf @@ -0,0 +1,111 @@ +variable "project" { + type = string + description = "Project Title" + default = "bookstore" +} + + +variable "region" { + type = string + description = "Default Region for Project" + default = "us-east-1" +} +variable "vpc_cidr" { + type = string + description = "Project Title" + default = "10.0.0.0/16" +} + +variable "public_subnet_cidrs" { + type = list(string) + description = "Public Subnet CIDR values" + default = ["10.0.1.0/24", "10.0.3.0/24", "10.0.5.0/24"] +} + +variable "private_subnet_cidrs" { + type = list(string) + description = "Private Subnet CIDR values" + default = ["10.0.2.0/24", "10.0.4.0/24", "10.0.6.0/24"] +} + +variable "azs" { + type = list(string) + description = "Availability Zones" + default = ["us-east-1a", "us-east-1b", "us-east-1c"] +} + +variable "AMI" { + type = string + description = "Project Title" + default = "ami-0eab4597a7e92d75d" #"ami-0077a350292f081f3"#"ami-054c337ee5048c313"#"ami-0dfda8a3ee7678578" #"ami-0dce57de6dcc3a6cc" +} + + +# variable "lg_user_data" { +# type = string +# description = "Project Title" +# default = <<-EOT +# #!/bin/bash +# echo ECS_CLUSTER=my-cluster >> /etc/ecs/ecs.config +# EOT +# } +# variable "lg_user_data" { +# type = string +# description = "Project Title" +# default = "#!/bin/bash \n echo ECS_CLUSTER=my-cluster >> /etc/ecs/ecs.config" +# } + +variable "asg_instance_type" { + type = string + description = "Instance Type" + default = "t3.micro" +} + +variable "key_name" { + type = string + description = "Key Name" + # default = "./bookstore.pub" + default = "Rumit_Key" +} + +########################################################################## + +variable "desired_capacity" { + type = number + description = "Desired number of Instances ASG can Create." + default = 1 +} + +variable "min_size" { + type = number + description = "Minimum Number of Instances ASG can Create." + default = 1 +} + +variable "max_size" { + type = number + description = "Maximum Number of Instances ASG can Create." + default = 5 +} + +variable "health_check_grace_period" { + type = number + description = "Instance Type" + default = 300 +} + +variable "db_instance_class" { + type = string + description = "Instance Type" + default = "db.t3.micro" +} + +variable "repository_names" { + type = list(string) + default = [ "host", "web", "auth", "migrator"] +} + +# variable "launch_configuration_name" { +# description = "output of the AWS launch_configuration" + +# } \ No newline at end of file diff --git a/Bookstore-Infra/vpc.tf b/Bookstore-Infra/vpc.tf new file mode 100644 index 0000000..bc4cc09 --- /dev/null +++ b/Bookstore-Infra/vpc.tf @@ -0,0 +1,98 @@ +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = true + enable_dns_support = true + tags = { + Name = "${var.project}-VPC" + Createdwith = "Terraform" + } +} + +resource "aws_subnet" "public_subnets" { + count = length(var.public_subnet_cidrs) + vpc_id = aws_vpc.main.id + cidr_block = element(var.public_subnet_cidrs, count.index) + availability_zone = element(var.azs, count.index) + map_public_ip_on_launch = true + tags = { + Name = "${var.project}Public-Subnet-${count.index + 1}" + Createdwith = "Terraform" + } +} + +resource "aws_subnet" "private_subnets" { + count = length(var.private_subnet_cidrs) + vpc_id = aws_vpc.main.id + cidr_block = element(var.private_subnet_cidrs, count.index) + availability_zone = element(var.azs, count.index) + tags = { + Name = "${var.project}-Private-Subnet-${count.index + 1}" + Createdwith = "Terraform" + } +} + +resource "aws_internet_gateway" "gw" { + vpc_id = aws_vpc.main.id + tags = { + Name = "${var.project}-IG" + Createdwith = "Terraform" + } +} + + +# Elastic-IP (eip) for NAT +resource "aws_eip" "nat_eip" { + domain = "vpc" + depends_on = [aws_internet_gateway.gw] + tags = { + Name = "${var.project}-EIP" + Createdwith = "Terraform" + } + +} + +# NAT +resource "aws_nat_gateway" "nat" { + allocation_id = aws_eip.nat_eip.id + subnet_id = element(aws_subnet.public_subnets.*.id, 0) + tags = { + Name = "${var.project}-NAT" + Createdwith = "Terraform" + } +} + +resource "aws_route_table" "public_rt" { + vpc_id = aws_vpc.main.id + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.gw.id + } + tags = { + Name = "${var.project}-Public-Route-Table" + Createdwith = "Terraform" + } +} + +resource "aws_route_table" "private_rt" { + vpc_id = aws_vpc.main.id + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.nat.id + } + tags = { + Name = "${var.project}-Private-Route-Table" + Createdwith = "Terraform" + } +} + +resource "aws_route_table_association" "public_subnet_asso" { + count = length(var.public_subnet_cidrs) + subnet_id = element(aws_subnet.public_subnets[*].id, count.index) + route_table_id = aws_route_table.public_rt.id +} + +resource "aws_route_table_association" "private_subnet_asso" { + count = length(var.public_subnet_cidrs) + subnet_id = element(aws_subnet.private_subnets[*].id, count.index) + route_table_id = aws_route_table.private_rt.id +} diff --git a/Project-work-Documentation.docx b/Project-work-Documentation.docx new file mode 100644 index 0000000000000000000000000000000000000000..8959ecfce0770b65b32256ca8ec93cc4e4bb7fbb GIT binary patch literal 13812 zcmeHug?9;2@n8K00sb{03d<2gl%n{jBT9s zl-=!&9d+p3tgQ%gL4hf90Ko6<|8M(WdzcDuFD>lQ z`Wj)9Z6+}M5FBRnvtns$$CP0MVjynljT={jme0t`2tZSR07OwGMyrx=4PYV&`Fz2= zc-B4lmp(^sPAFOqfP1#0IQ zw;W@w53sA2tlCSuq;fMxx42}dhkDR!1KijSZo@gchA-ZM{ipT*8E<>pXrfh_KJ z=~}Qo4z(C$$?Ka@2>-igy}f||KK{cz@nW%>&fja~-mMSj-8}UijIA8$>3-Y)pIQDd zw#C0)dU;&;yIH~toCmxFOtmX4cVpzq(Hl;#U@SpEYDvnVtSnkAyuR=(E&}Tu>Why~ z&%{r9IA(}BZzSkkU?r)*g||b^KkD{sJ-4|55`)?b7~SUWwqVou?o3`rh$SgU0={UV zg->9C$3BFnjCY~zQi(gL3vAqWyL^Gnh|SI4vlruHg)8Y*ICpb7DNN} z6)KGW*~>&5S@U&2d=HbK?VM1fUT@PDJ3~kC`rS$Wr+w6Qe8B~KuW1AU0B``1K(4k9 zM)dzO5+hqfXX|%Q_L~d)(_nz!{n&f!|J_@8{P=I+x(45D01sZqGbvr9?EeTj~VXd>RCJ=uA-6mKH zxw%D2xgZd+$KuYt>oL1>Tu;pTQ+HjAL7QKcnY#U18GOS{817_X@&FetrNqLOplWul zXotiMsp|~vP!-csCHjWTEpfF0&ISKcqxloGo^`{5*?}0?y#y-THK9@~x@6a|q!9YW z3gnk!NEWILV4PcRZ}hq!C(dDXR$hc0T8@aLDTt^hm%w%johEF97dY`>Yl5LQN}&dq z(gNJVp)P}VpR?mLQpgc~Zdb1G-=F<|s!_t|zKQ`j0MJPd03f}$_+5?uu0SW6*0w8b zh_8H~-uw}(Z6%Gk=^Z1vL=CD+{9SIhKwmf}k!kG>?*31Ci%dj zK$ZuuD~oJ0E92OX;Kb_eH3sGiL$JhAGE{66gS|v4c|xzzO_=#P@u~<*pDncsW14<^ z(-amCr7IwgZP%k@=ESn3d$68}={W2t zUo@uAp^uEFd_u!MPJvDh#W&HyV#FGcKSJOb-XCQYPi(811Zi2c(IK)$4$pKKCeNWg zcK~19nxIHp6Qc@GpiD#>xBJe1EJCrgJF%{qSt6gwiN=q(niuFA$rk9dE^>wt3vQX4 ze`nelH)7=WsLdoGJftFTJI4Ym%@ga{O2~2Z%m;Hmjc503H@?!(2;rZ${6hPfXlol6 zBota6F7325gMrk@_e?hwTh~$Cr*%u7*29i^uVV3o>@YkjnZZ zrVz_Wlfl+e$_|K?#(27CmK4|t7l_W76*Ywv`c7i3{v(VX?|O&@t=rCmAh54;TwR1u zU=hCuF>=@{7kJt>T~ILkCNc`H-5Q9)>8H!rq0+PDmnnxIUu)?Yt!@^ykd?#-XKQ&k z)aBgbK$uxvN5oLoW$!|bqju760!OwHW<@DEQD%+MJ4F^(awCdArL?IF#?0XuLSWVm zF_;c>QH1!W0{MVWsiHGSaYnkQ~TERVaP zZL*PolZBl#@Ae<7%O(H7tZ-{d(+Rg#TA@K@&Y4rVS;Zowvs<<(r%TNY7QJYI__Q}- zUO`<{7$U@l8rGi;Yt%;>3WhQP{OSDjH}=Pr3{f8lu-L_2kL1Y( zW>`P8cr6E_E;6BUAP>CMc;I}?>Yo~O8%UP|tQq^-sC>;YXg48{Zm9u;sX^9<-!&rR zI>HNMIxb;r^K{~DQ&wgJYUv2*AWiYbVv2On;>tld8E3iT#o=c(CF)FYsAE^OPP5fo zcdH#n#{;JkcdJquQSS-*#DXms-2gjI#xXjYFKjCyXEDihE;Wf>KIhHLBWH2-zD(sE z3PY;krstswpEWw2@3d^r&`G*KZ%4D+^*_k--M&%dWmSx@=c}PRducXH1KBAj6j&5c zl}G81D0~U~mT(X)AQen>4Rr`tC27N0V8D)KSVKx4x$ea=W`}U5NHxqj+&s2sQvn== zY`~g)Y~cr{uRol!IdERAELv7$498QXZ>hiU6697C4yR&`!*1OK)=#2DHAoHvE6DnB zPfW=-*d^Mtau27huIxWeP>btrE-)OqWK;p{;6hn(E5n^arh@M~x4u;#}ON#@`1lL+mzA z30gKc8eX4qb$C6`T!$Sj$qt`Ulz7~|r7GPYz;JYr7uA}PjIv7|Krk4=zF$OuJ)@Zp zzQStCo>g?MNyXyB&)a&-cwob(w- zj}R_XPEk}+_R}Jj!OA!1FLv+@U4qd=$G9fIU|`ne9_`yop>FKYMZ!L;YAJotRaI1K zJLPbg<=BWH9OB}UNUO2AxDVngnyQZ}IC>5d2@B796&`+lx`i*Fy|^u{?%!^D_J&hc zW!E;yHDR^&Hwqm5>DZ-y4W`?Z;i~AyXO7=E3P$KIZeOj0wHzJQ2AWeoTcJBRA{eUP z4#QUBEsGfhT8#bk$Av&D#jZt_D(vGRlMbVD>9jHp&YFj&r=0XNPO z1ZZlc7ejkd8tt_<-WP=b=8#GiN!N+r9nu#p008YzhvZ~tY;8>c`=0T)S=$fy zjE)aa_?f{$$V zn;0O;S&avCr=};Jd7Gwmp40$DZ1w?rIgDmBFO&dSV9uG0FHyH1r@HG}pIATY)<2xF zKi`)6FZ^6!ztEw7#&820`EmyC!*Qd=I4j^jRo9oVajgSVCE#9D<8Q0l>fL0%fkr{b zk!h7ft($lDp-x9=AVIsXXC;fz2i@ltKEM#e zfsf2%fN`=(kpyE4tu$inSfGljdUWM`XRjhD3VKFRtg$Z(v~D)?0KjA5LBriQJe->% zu+@Ei*xPlqdphVp%+r&@=jZdjsp~(&k|AC@y1m!?`EsLlv2cB^%l|kMwa@i>pUwAt zxmy7 zEHS)N%98pd2om-IwxdK2*n+SL=s6Q53fsoql6Sy`;Ft-pmfym5AK*+$$|FHz--#Dn zI=Y7@@+6=xHR>DOW~{Z9FKuVQl5(Z1mE+;yP>%bTp@d$@0tvtJXgS2%l(pW)&|^M$ zj9n+1E?!sCzGRF+rVC zcCfo;gC6I7d@~%0_)x4PFEo7rpl|`(FG99E*<6CebTZ&|^4dE0G)&#*< z6Bjy~Wpwn4u@_bJBgmZtInhFDXt_CvdW?wqAXFdG9OeBBFLUZb)^8?B zJFZ~b|0wEyDjkOp-O??wz^)=YIOx!R^mfn)y8_LX*E_IJn5htv%hfuz_gDok?c6jiFS=#3ci8QUotx~> zy&dNb%e^7+9EzWswC>jia#Spoy*qcGoUR z|MxIQN{xnn<~>qoh5lQZ<7n*UWNu^X_?t(mS6#7L;Xw4(`}8I_c}vOmUOz5$mpA5RNY<+G%bgd_#ypaRz5Omz`bU9}3&IVF)86=}U$1l+Lb zObqlrKcA3JToa5&SgCOecCvjwLl73|0yj~Eso`5PTvsOflCLd_r&#+sVHo}5nPBk~ z=T_X6m7j8Mk7h3z;}HyrmqbQ8fyZ}mu$E_9E-FZQgC?#DuPZ~)LzvGc1N>r)hm>MZ zLl^Pn(>HO%hdG{#RQX)pxBK0tKW#>Xgh+!NHnf2kH8)COYz24q0zY*VCabI1%}o-S z<3!N_AZXHh2m*lSk(G`-3Ue`lP6^kk`UFmYY#CUr<|L&-D5Hx>*ew$mzb-4Oa_4_E zuBIX>^|PjIR`~4Esp*Ai0+pG!VekyOAl_Sg;DJoUVNji2$F~xSwKAe9O2_Ypy-`aZ zKa;RdhRh(0T5=h{JJ^FmsVjU5_V{_f&1c<0*l)^tMPB)$9hjjhmA_}4ABR-e&`6V` zP%mL@tuGC^g-{C0dC+olfL>ZG4V+p+K)@0^20>wKK4&WRBRZV`UU&RS$>^$#^7<;tL7 z*xV-K^LfhAFg?7w24=2a;i%S!-4sWFal``414e}A0Yi7$dhN}DibL>{)2;&AluJ+XZWl2MTzQBTx(X7yGJJPPjqGAvo#oW?rrY>v}mzF=%N0RG06w8FNpKqe1q}~Q}(j3KZj%UO%9;WDCCOR+U^SvC^Q}SEE$h!CNM3G zVr)z5K9OJATGZGIiV8&a$0qiMb{;_&iS%N`Twq~*<+%&#jiad&b%w|Ur8bpcupQi< zTXBYvU!^v{6WEEiJI}VS6Tn*F-4iC)Iaru8N~GP}R$<8|nW0Fp+Bz`lP#5`fOX7|u zAAiH8^}5Dh^4974{64MxFAE*$*Br>g^BM^Gh|=q}OrS`V zSM%hr^Q}ttQCD2X4qY_v3|{VL-aqxl)ZPeg=unijf|2()6yznJi;v_neEf&qyB_cv zj5(rHaG-i20^3y&j-F-nt<(fb!t#x%=6XHos0);No?O)xJBDW&jjr9Tg>%j>H|3zO zP0z3Y`3$Om+4Rln@tEHHIqv7{pfpW@ z9?y87)OWG?_uPRW`PXofD>t^MOS{{%UWy@yBoUd?!0bp>9_*LP!*k!~mq(USwK!9M zaFQi+oUBN6_YNN1=sLS@!=!dK>8yhQ$HW1e1^Ssm`?2XFgGV~$%eY+wviu>n?U(lU zZG|Ao=Z~{qbV9EhL84>vs{_$WbgT-bOF}etX?Lq3cjeP&siiTdsWwhD(z;_JwF5Hn zKoscurA(|7>&FAithE%ld`oq2{SF`;JwSIr8ywpleF{Msre!Ie5uZ7D+TCT8M@$ss zo#KIY%DO&_XV<62Li1m9QX6HZCLelhwwLQJ@vBh{*KIr|7=J}XXhp|DgOP3LlV@U_ z7Uvkfx|XS)4}iIrKaOP>#kSQ8m53N9>^t#&+Mp+!5PN>U;+a3c+*upf zsUy3qz*oASmvNjBqw!|vW%u7M)!$tgb4;$%Tl$<#gW8D0HqnAt9KX#AlGyWL@OEJM zvgN7ok%HDz3SHxOb&kai&RaQHg*QGkt^7oh?W?kh5?$EBXUA4+e8}#*x>_Vuu*_ka z7u_iHvX?Npr740WkA*LTPPE1Uf#)&y((L**Tg~M{%{H)9;^qy49EBU&2;JKq>Tz0K z5??lDYUews#Py+&6?D_)TZMkSR9NE!n5hXL6j>Eg;}b<+N7xWng|#Vq)v^tL5Y@p( z1vHme+2^Pqr8(P8R0lg1&`%etU>ak38ZiCQUf6H<> z;mLD2qx_@xI0hKgwlNAJQe>Q(h2IW6HL+gtE=Gk+cF#cd_@}D<7Rh~JR)oE+8VP3`zWObm$NqxvaUkfTY2uJF?z;I=c$b(n zl-bt_nu6^<+s>QRhK4_a|Tskp&rw4bclN+~!b; z)A2jPt=#l5CF~R=hQridS%k-GgZIJFnR!^NQ#Mom`(fo_vClgPSO2i#A=7Wza|8bA!9gjL8SYGD z1W&0^8+5PSiMw&h#ESC+l_nShs^xN;NJCYV@Qz}WCQ1JV{93L;s^vIw?LwNkQ8lY= zbHB|k+mh8}rTJB=Ipe67tfgeT47YJD1$%w(+Q8K&v6Z18b*zaIo7o_8#0Ga=CaZ(u zm{+#Ay%De3f)io|D4DKx$*ncjsmQh8_1JC+ouf_&u(o?K$NYvn}43iC&A zYDxN0->_tuB&c+1Yr-e`+gq^PjU-wK_5)p}*vW&tU7fK&*aH{D1%xKO0Eh+_^TI<6 ztc{=%N3?jxGG_nImz_G_N#zS|^6ugA;)Z>Zq7a%BMMN4VQb|z}lJ;@bFaix>+H*ZG zRwbH9GqZb2`g8qZP9*!0E#sC$f{UcA581arg{!e^+kESkQ?7-S3q1(@S>>=z`cL?I zaQNodO8j=lCYy+w_VG_u7sO^ZgKbe7?RuLaRb|_GQZA(@7im{725dHqu{!#!! zeKw^cVq&C;kb;8Ai|g(wW~pXS1PWgNI^ze|DO&`D?NcQOMZ65m@=19+!s(H`b!5g* zA(9y;56cu?jP*NBghd9f7yN@)C40A%vmHUwYY^G6dz&()w3^5eeH=g2XOw)> zQ$7{PYr6ock@RM$m_3{Zl2xl@4yst+D@=_!*e|uZpr#_J2~tRzS}QahY!1&q8g%sU zB%``jnv-dEU`UCxxR!UK<{q!=y=b?E9GVgv;;eBvt&?%p|V`m)n&q#Y#PLugq1!ZTcw;$oM#$F| zO~BmYKBlV||8?^=Bb|d1gNuWb$MZbIrZew%ARU>uTr^lo;$OOEQPxyh0N8g8S8- z_u~9j$Qvs#HPBTnux3j_~?>9hFE4$$83A(Qg|1H>y8JWaAM2EN? z6UrqsM5oS{8JQ*^L?^!T*4SGqTsKOV6GK-_wU3SJAD18ag zfSyI@RT5V2&-`F%OjQesE-5%D^%4lWA-RaoS;SphR*^?ZICfX{5<;Ngal!utfom{n z{s|)NmbUP_OvcJ>D+n&8D>>Cjpe<2=X7902uNEV)5wro_BHI_s5pSFgy&Fry0-dme zvQV*%8cj;eZGrNhb3}O>IUviqaWc5*X;m-_TBG1Ev0qY5|Kl>YYe|b{o2^QeEVLVE zDOu^X9Hb*hla})bjHRs=pXI;XykF-3uX?+-fbGvq`lHQG5>_&8gXCW#{zv)03$R~` z`}fI@5|(3WBeR=){D!Mf&(jYP_RC1w*Spu;K9Ny%2GqAgn(@1#RQOU1gQll}&dDpP z*segUeSjTBwkDvV(mtr|Ia-Ow5mj0L-0BlC=mT+d{25m-(So}6@blA*O0o@IP*V0v z>&cSF`Kptv@sw-!kvIJZil>e6!an;xkIKA+)& zHsjr*x#<^$;eB^^r&?3|G+$>gRiC$XX1)fLAQXlll`@aDPn!(lzRBy5dOF=M@RHlX zNoS2sO0cKqu4|1awUv>Sj@LU&$5I;WHt@;qBTka*5l#MypBfNK%Vx0S4{as_r`GY- zSJF>#PZrd2%hsJyT)Z%x$8|{NP|?ytp7gFh4<9{l)z;k7x2Qx`zK!y|yf1wGXL@&a z=j5a1d(^Y}9`zu-vjpBVyB{5F?HuV1Z5@n%TlaY9()=Ga;5~?mh#R#D_>JO~_=GUz z$pBu004m~5Q6^S8t6b6Qcq+LCjy>ukbbZf?D4|eyU^8(4UBB6d?>R}O8p2St0$L;& z11v{aoSTV@Ez_aqL2Auraz8}IG zj zrHFuLx4}o$n?i8yCM!X^PTI|Lh2}l4gikH9$j;_x&YjC&JZ~KqJkNbBW}^l#9^)L% zutQ7WBm4f6LI}D`=Sy# z#19>XWAs-kHQU+!oVB=<2^eqva4qF!`C#NA%Pjc7exG-u>7g~hXw zkdPcQb&L-=qYvVr>c&5!e8l_kz|Kghv;{cGOXVB6g7|M5T9tx7$_xqskdgoZ$bUwY zHqO=t#t!dmbN@>Atk`U`A$rqK`Z9ptFizRgA*Swk_X#s44)#p%f6XcEk&&*$Yu*4z}RO&}&7>}`$7D?yA-%!QBX z%ljT1hT~B9X=b>}5ap zrGBbA;WFqms>2M=X))ko!yRqB%2xbNL=o860^tM|sL&%iaXM)LuR5&(RUW>-s+2CDKzwd53c z0dYH*3cHJKGr54IIrOKs$clvm5}1N#J(T`^rV0rtL94qxxkckpgEudr2nZ3f7-BOS zu~0pJ+^l^1(O%^>vQD&BPa0_>*e%`Qzmo@Pky4AS`jd6uR?fUH^9{&qe$%i7C2z^yzzMg%L<4)VR z`QuLUIjZ2gX`YvWGKgF%*#;h_CpkPx-~*mFs}}zPtMTQOyiki%@xtz=5U zjtg4DE8l^0kvW2y2~sJ$9Z;DOPDoK)0-)~Q$WrJK8uy)vx3XwCKDXV2RW>fV)nJ7c ze#MiP3n1m!i`Bz?EYTDao#MWk;BRf?beW87>MUqblrs3zu*S;!d6gH$8TfNO#28fX zn`ciOFUsB7teJeq&Sp7zWlGg`I>*GRn^EU|ADAhZeaGA-g>PrrarSQC<4ct3EkauJ zg$y~}1sC4tpnOJ4p2-~dkAzkWKtn<0ec9=^c>9k#>ncaBm1q&RFRF^J)3p(d5r{y$ zqq?(~AD%Uvq9E#Rq}?qLxY$TdsE7EwW#$ch#iCrqLX<6~Y0cMhMzv+P z*ZP{{uN{U#O&ewUB>sGN4K=-jz%=wCf?ssXz{XPgWV=O9S$n4FV?_JJY z6OXS(>$neA(4{6@mgMt`nd3y8f3a`+Xq6>R$EVpb)APWAIMXN#E=pzTWRRQ(G9S(aOMeEkDb!IA*t{Wu=dI8M%JcE72f+w92*GFwg^bd~ zp_uZzGq)@^v|)C9h+xu0}j?OMf5yJfp+%7q(bX z%Nw3JmjT!bVjjD~{n(u*#)q+Dq)DVpq6WZ~sPc}b`XebZWurZh2nyz_;)!u`WFYxV zdn~!O<5Rt6JC=rl5fiTW0Y=MPe5_qMOU6nh&FOc^O$%~0E1AhI?`p-BA&;d=7vfb? z+!mhD@l(3mO|9mKDEx>gVJI~@sr@5+#l`O{z6Uupsd1L%RSA7m2|{a3G*R_xk^-ba zPq;BBiVFElJJ#?LhM&EXR(kYq6|^4Yz3Xw+f4;k;|J0&BB0XK#_o>wIyCxz2eJZ7I zXZL?IsdvNsW6MyKm49dDwi93Cm;TULw#LH0V){zh=TBdPn~^tbO*~B^#nku8OJ`gV zip{w+aX0EI_vw1-a0l1><9zvyS5g5KmN~U?nsY0)?F8L4iHJVc*NSdukFFS-heIeH z&*Mq)AVNZDlzDUPyl)0HL+IQ{CdzEeB^Bo)`Lpa}^9^xshPxfYC`&w}s<8Y-Y0zcN z?8t>6+jE{j>CKIetLWPjAjG|Jq)sx>ksb~COZ+1N32e|hrSOk&8;t&@Q=c{*KM4b> z=G7ADODrMvSD@*Q@^>%2>|21@fHsynEAm6@)n0S*o4V&2-Jg)tZ$P^WX$8Qc6FjT7 z{7t(GDOah5p|5Zj5o6itvFNq2A96o%n0%D@YA^l6oz_X<6F!a9^Iv~ zp8bWcL)<3-CB2HOF$kshL!D-ZJe<7LN5pV6P7Kxv!0J?h+nSz0U19gp;&szx)rIjx zs?!=x-gQGAvDm;MeJ}sJO8!e(fq-e>GaLW?^_D*$&Y#Es^0LcE>3<6NCjtLY;BUw5 z_eAR7==i?^|H(}LJFxSe*YiK|lz)}?NqS>RBANcuZf^;h_>RHDD&$?tiK-#hXv zvFKM3zvhkq5^?%YPWo5o<8KM1U*W$do&JKKWBvjEB?a}XgkO^_e@WoP{<8!BPQCmJ z{%7>}7Zd>4B?AEdBNqG>{_E8IF99Kxf1LXt6ZK#5zxv(3@JF