├── .gitignore ├── ecs.png ├── modules ├── iam │ ├── variables.tf │ ├── outputs.tf │ └── main.tf ├── route53 │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── security-group │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── ecr │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── ecs │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── vpc │ ├── outputs.tf │ ├── variables.tf │ └── main.tf └── alb │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── .idea └── .gitignore ├── sample-ecs-services ├── frontend-app │ ├── .idea │ │ └── .gitignore │ ├── Dockerfile │ ├── package.json │ ├── server.js │ └── package-lock.json ├── customer-service │ ├── .idea │ │ └── .gitignore │ ├── Dockerfile │ ├── package.json │ └── server.js └── transaction-service │ ├── .idea │ └── .gitignore │ ├── Dockerfile │ ├── package.json │ ├── server.js │ └── package-lock.json ├── README.md ├── outputs.tf ├── variables.tf ├── terraform.tfvars └── main.tf /.gitignore: -------------------------------------------------------------------------------- 1 | /.terraform/ 2 | -------------------------------------------------------------------------------- /ecs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shashimal/terraform-ecs/HEAD/ecs.png -------------------------------------------------------------------------------- /modules/iam/variables.tf: -------------------------------------------------------------------------------- 1 | variable "app_name" { 2 | type = string 3 | } -------------------------------------------------------------------------------- /modules/route53/outputs.tf: -------------------------------------------------------------------------------- 1 | output "internal_service_dns" { 2 | value = aws_route53_zone.private_zone 3 | } -------------------------------------------------------------------------------- /modules/security-group/outputs.tf: -------------------------------------------------------------------------------- 1 | output "security_group_id" { 2 | value = aws_security_group.security_group.id 3 | } -------------------------------------------------------------------------------- /modules/iam/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ecs_task_execution_role_arn" { 2 | value = aws_iam_role.ecs_task_execution_role.arn 3 | } 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /modules/ecr/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ecr_repositories" { 2 | value = [for repository in aws_ecr_repository.ecr_repository: repository.name] 3 | } -------------------------------------------------------------------------------- /modules/ecr/variables.tf: -------------------------------------------------------------------------------- 1 | variable "app_name" { 2 | type = string 3 | } 4 | variable "ecr_repositories" { 5 | type = list(string) 6 | } 7 | 8 | -------------------------------------------------------------------------------- /modules/ecr/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecr_repository" "ecr_repository" { 2 | for_each = toset(var.ecr_repositories) 3 | name = lower("${var.app_name}-${each.key}") 4 | } -------------------------------------------------------------------------------- /sample-ecs-services/frontend-app/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /sample-ecs-services/customer-service/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /sample-ecs-services/transaction-service/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /modules/route53/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" { 2 | type = string 3 | } 4 | 5 | variable "alb" { 6 | type = any 7 | } 8 | 9 | variable "internal_url_name" { 10 | type = string 11 | } -------------------------------------------------------------------------------- /sample-ecs-services/customer-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000 12 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /sample-ecs-services/frontend-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 8080 12 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /sample-ecs-services/transaction-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 8082 12 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /modules/ecs/outputs.tf: -------------------------------------------------------------------------------- 1 | output "aws_cloudwatch_log_group" { 2 | value = [for log_group in aws_cloudwatch_log_group.ecs_cw_log_group: log_group.name] 3 | } 4 | 5 | output "aws_ecs_task_definition" { 6 | value = [for taskdef in aws_ecs_task_definition.ecs_task_definition: taskdef] 7 | } -------------------------------------------------------------------------------- /modules/vpc/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | value = aws_vpc.vpc.id 3 | } 4 | 5 | output "private_subnets" { 6 | value = [for subnet in aws_subnet.private_subnet: subnet.id] 7 | } 8 | 9 | output "public_subnets" { 10 | value = [for subnet in aws_subnet.public_subnet: subnet.id] 11 | } -------------------------------------------------------------------------------- /modules/vpc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "app_name" { 2 | type = string 3 | } 4 | 5 | variable "env" { 6 | type = string 7 | } 8 | 9 | variable "cidr" { 10 | type = string 11 | } 12 | 13 | variable "availability_zones" { 14 | type = list(string) 15 | } 16 | 17 | variable "private_subnets" { 18 | type = list(string) 19 | } 20 | 21 | variable "public_subnets" { 22 | type = list(string) 23 | } 24 | -------------------------------------------------------------------------------- /modules/alb/outputs.tf: -------------------------------------------------------------------------------- 1 | output "internal_alb-dns" { 2 | value = aws_alb.alb.dns_name 3 | } 4 | output "internal_alb_id" { 5 | value = aws_alb.alb.id 6 | } 7 | 8 | output "target_groups" { 9 | value = aws_alb_target_group.alb_target_group 10 | } 11 | 12 | output "aws_alb_listener" { 13 | value = aws_alb_listener.alb_listener 14 | } 15 | 16 | output "internal_alb" { 17 | value = aws_alb.alb 18 | } 19 | -------------------------------------------------------------------------------- /sample-ecs-services/transaction-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "express": "^4.16.1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "type": "module" 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Provisioning Multiple ECS Services Using Terraform 2 | 3 | This repository contains the Terraform implementaton for creating an ECS cluster with multiple ECS services. 4 | You can find the step by step instructions from this [article](https://medium.com/towards-aws/provisioning-multiple-ecs-services-using-terraform-d4448354c803). 5 | 6 | ## Architecture 7 | 8 | 9 | ![ECS Service Communication Using an Internal Load Balancer](ecs.png?raw=true) 10 | -------------------------------------------------------------------------------- /sample-ecs-services/frontend-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "express": "^4.16.1", 12 | "node-fetch": "^3.2.0" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "type": "module" 18 | } 19 | -------------------------------------------------------------------------------- /sample-ecs-services/customer-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customer-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "express": "^4.16.1", 12 | "node-fetch": "^3.2.0" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "type": "module" 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /modules/route53/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route53_zone" "private_zone" { 2 | name = var.internal_url_name 3 | vpc { 4 | vpc_id = var.vpc_id 5 | } 6 | } 7 | 8 | resource "aws_route53_record" "internal_service_record" { 9 | name = var.internal_url_name 10 | type = "A" 11 | zone_id = aws_route53_zone.private_zone.zone_id 12 | 13 | alias { 14 | evaluate_target_health = true 15 | name = var.alb.dns_name 16 | zone_id = var.alb.zone_id 17 | } 18 | } -------------------------------------------------------------------------------- /modules/security-group/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | } 4 | 5 | variable "description" { 6 | type = string 7 | } 8 | 9 | variable "vpc_id" { 10 | type = string 11 | } 12 | 13 | variable "ingress_rules" { 14 | type = list(object({ 15 | from_port = number 16 | to_port = number 17 | cidr_blocks = list(string) 18 | protocol = string 19 | })) 20 | } 21 | 22 | variable "egress_rules" { 23 | type = list(object({ 24 | from_port = number 25 | to_port = number 26 | cidr_blocks = list(string) 27 | protocol = string 28 | })) 29 | } 30 | -------------------------------------------------------------------------------- /modules/iam/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "ecs_task_execution_role" { 2 | name = lower("${var.app_name}-ecs-task-execution-role") 3 | assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json 4 | } 5 | 6 | data "aws_iam_policy_document" "assume_role_policy" { 7 | statement { 8 | actions = ["sts:AssumeRole"] 9 | 10 | principals { 11 | type = "Service" 12 | identifiers = ["ecs-tasks.amazonaws.com"] 13 | } 14 | } 15 | } 16 | 17 | resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { 18 | role = aws_iam_role.ecs_task_execution_role.name 19 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" 20 | } -------------------------------------------------------------------------------- /modules/security-group/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "security_group" { 2 | name = var.name 3 | vpc_id = var.vpc_id 4 | description = var.description 5 | 6 | dynamic "ingress" { 7 | for_each = toset(var.ingress_rules) 8 | content { 9 | from_port = ingress.value.from_port 10 | to_port = ingress.value.to_port 11 | cidr_blocks = ingress.value.cidr_blocks 12 | protocol = ingress.value.protocol 13 | } 14 | } 15 | 16 | dynamic "egress" { 17 | for_each = toset(var.egress_rules) 18 | content { 19 | from_port = egress.value.from_port 20 | to_port = egress.value.to_port 21 | cidr_blocks = egress.value.cidr_blocks 22 | protocol = egress.value.protocol 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /modules/alb/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | } 4 | 5 | variable "internal" { 6 | type = bool 7 | } 8 | 9 | variable "vpc_id" { 10 | type = string 11 | } 12 | 13 | variable "subnets" { 14 | type = list(string) 15 | } 16 | 17 | variable "security_groups" { 18 | type = list(string) 19 | } 20 | 21 | variable "listeners" { 22 | type = map(object({ 23 | listener_port = number 24 | listener_protocol = string 25 | })) 26 | } 27 | 28 | variable "listener_port" { 29 | type = number 30 | } 31 | 32 | variable "listener_protocol" { 33 | type = string 34 | } 35 | 36 | variable "target_groups" { 37 | type = map(object({ 38 | port = number 39 | protocol = string 40 | path_pattern = list(string) 41 | health_check_path = string 42 | priority = number 43 | })) 44 | } -------------------------------------------------------------------------------- /sample-ecs-services/transaction-service/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import express from 'express'; 4 | 5 | // Constants 6 | const PORT = 3000; 7 | const HOST = '0.0.0.0'; 8 | const INTERNAL_ALB='service.internal' 9 | 10 | 11 | // App 12 | const app = express(); 13 | app.get('/health', (req, res) => { 14 | res.status(200).send('Ok'); 15 | }); 16 | 17 | app.get('/transaction/customer/:customerId', (req, res) => { 18 | let customerId = req.params['customerId'] 19 | return res.status(200).json({ 20 | customerId: customerId, 21 | accounts: [ 22 | { 23 | id: 'C001', 24 | acNo: 'AC0001', 25 | type: 'Savings', 26 | amount: '5000' 27 | }, { 28 | id: 'C002', 29 | acNo: 'AC0002', 30 | type: 'Fixed Deposit', 31 | amount: '15000' 32 | } 33 | ] 34 | }); 35 | }); 36 | 37 | app.listen(PORT, HOST); 38 | console.log(`Running on http://${HOST}:${PORT}`); -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | #output "vpc-id" { 2 | # value = module.vpc.vpc-id 3 | #} 4 | # 5 | #output "private-subnets" { 6 | # value = module.vpc.private-subnets 7 | #} 8 | # 9 | #output "public-subnets" { 10 | # value = module.vpc.public-subnets 11 | #} 12 | 13 | #output "internal-alb-dns" { 14 | # value = module.internal-alb.internal-alb-dns 15 | #} 16 | # 17 | #output "internal-alb-target-groups" { 18 | # value = module.internal-alb.target-groups 19 | #} 20 | # 21 | #output "public-alb-target-groups" { 22 | # value = module.public-alb.target-groups 23 | #} 24 | # 25 | #output "aws_alb_listener" { 26 | # value = module.internal-alb.aws_alb_listener 27 | #} 28 | 29 | #output "ecr-repositories" { 30 | # value = module.ecr.repository-services 31 | #} 32 | # 33 | #output "ecs-task-execution-role-arn" { 34 | # value = module.iam.ecs-task-execution-role-arn 35 | #} 36 | 37 | #output "aws_cloudwatch_log_group" { 38 | # value = module.ecs.aws_cloudwatch_log_group 39 | #} 40 | # 41 | #output "aws_ecs_task_definition" { 42 | # value = module.ecs.aws_cloudwatch_log_group 43 | #} -------------------------------------------------------------------------------- /sample-ecs-services/frontend-app/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fetch from 'node-fetch'; 3 | import express from 'express' 4 | 5 | 6 | // Constants 7 | const PORT = 80; 8 | const HOST = '0.0.0.0'; 9 | 10 | const INTERNAL_ALB='service.internal' 11 | //const INTERNAL_ALB = '127.0.0.1:3000'; 12 | 13 | const app = express(); 14 | app.get('/health', (req, res) => { 15 | res.status(200).send('Ok'); 16 | }); 17 | 18 | app.get('/customers/:customerId', (req, res) => { 19 | let customerId = req.params['customerId'] 20 | fetchCustomer(req,res,customerId); 21 | }); 22 | 23 | async function fetchCustomer (req, res,customerId) { 24 | let response; 25 | try { 26 | response = await fetch(`http://${INTERNAL_ALB}/customers/${customerId}`) ; 27 | let data = await response.text(); 28 | console.log(data) 29 | return res.send(data); 30 | } catch (err) { 31 | console.log('Http error', err); 32 | return res.status(500).send(); 33 | } 34 | } 35 | 36 | app.listen(PORT, HOST); 37 | console.log(`Running on http://${HOST}:${PORT}`); -------------------------------------------------------------------------------- /sample-ecs-services/customer-service/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fetch from 'node-fetch'; 3 | import express from 'express' 4 | 5 | 6 | // Constants 7 | const PORT = 3000; 8 | const HOST = '0.0.0.0'; 9 | const INTERNAL_ALB='service.internal' 10 | 11 | // App 12 | const app = express(); 13 | app.get('/health', (req, res) => { 14 | res.status(200).send('Ok'); 15 | }); 16 | 17 | app.get('/customers/:customerId', (req, res) => { 18 | let customerId = req.params['customerId'] 19 | let customerName = 'Nick John' 20 | getCustomerAccountInfo(req,res,customerId); 21 | }); 22 | 23 | async function getCustomerAccountInfo (req, res, customerId) { 24 | let response; 25 | try { 26 | response = await fetch(`http://${INTERNAL_ALB}/transaction/customer/${customerId}`) ; 27 | let accounts = await response.text(); 28 | res.status(200).json({ 29 | id: customerId, 30 | name: 'John', 31 | accounts: JSON.parse(accounts) 32 | }); 33 | } catch (err) { 34 | console.log('Http error', err); 35 | return res.status(500).send(); 36 | } 37 | 38 | } 39 | 40 | app.listen(PORT, HOST); 41 | console.log(`Running on http://${HOST}:${PORT}`); -------------------------------------------------------------------------------- /modules/alb/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_alb" "alb" { 2 | name = var.name 3 | internal = var.internal 4 | load_balancer_type = "application" 5 | subnets = var.subnets 6 | security_groups = var.security_groups 7 | } 8 | 9 | #Dynamically create the alb target groups for app services 10 | resource "aws_alb_target_group" "alb_target_group" { 11 | for_each = var.target_groups 12 | name = "${lower(each.key)}-tg" 13 | port = each.value.port 14 | protocol = each.value.protocol 15 | target_type = "ip" 16 | vpc_id = var.vpc_id 17 | 18 | health_check { 19 | path = each.value.health_check_path 20 | protocol = each.value.protocol 21 | } 22 | 23 | } 24 | 25 | #Create the alb listener for the load balancer 26 | resource "aws_alb_listener" "alb_listener" { 27 | for_each = var.listeners 28 | load_balancer_arn = aws_alb.alb.id 29 | port = each.value["listener_port"] 30 | protocol = each.value["listener_protocol"] 31 | 32 | default_action { 33 | type = "fixed-response" 34 | fixed_response { 35 | content_type = "text/plain" 36 | message_body = "No routes defined" 37 | status_code = "200" 38 | } 39 | } 40 | } 41 | 42 | #Creat listener rules 43 | resource "aws_alb_listener_rule" "alb_listener_rule" { 44 | for_each = var.target_groups 45 | listener_arn = aws_alb_listener.alb_listener[each.value.protocol].arn 46 | action { 47 | type = "forward" 48 | target_group_arn = aws_alb_target_group.alb_target_group[each.key].arn 49 | } 50 | condition { 51 | path_pattern { 52 | values = each.value.path_pattern 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /modules/ecs/variables.tf: -------------------------------------------------------------------------------- 1 | variable "app_name" { 2 | type = string 3 | } 4 | 5 | variable "app_services" { 6 | type = list(string) 7 | } 8 | 9 | variable "account" { 10 | type = number 11 | } 12 | 13 | variable "region" { 14 | type = string 15 | } 16 | 17 | variable "ecs_task_execution_role_arn" { 18 | type = string 19 | } 20 | 21 | variable "vpc_id" { 22 | type = string 23 | } 24 | 25 | variable "private_subnets" { 26 | type = list(string) 27 | } 28 | 29 | variable "public_subnets" { 30 | type = list(string) 31 | } 32 | 33 | variable "service_config" { 34 | type = map(object({ 35 | name = string 36 | is_public = bool 37 | container_port = number 38 | host_port = number 39 | cpu = number 40 | memory = number 41 | desired_count = number 42 | 43 | alb_target_group = object({ 44 | port = number 45 | protocol = string 46 | path_pattern = list(string) 47 | health_check_path = string 48 | priority = number 49 | }) 50 | 51 | auto_scaling = object({ 52 | max_capacity = number 53 | min_capacity = number 54 | cpu = object({ 55 | target_value = number 56 | }) 57 | memory = object({ 58 | target_value = number 59 | }) 60 | }) 61 | })) 62 | } 63 | 64 | variable "internal_alb_security_group" { 65 | type = any 66 | } 67 | 68 | variable "public_alb_security_group" { 69 | type = any 70 | } 71 | 72 | variable "internal_alb_target_groups" { 73 | type = map(object({ 74 | arn = string 75 | })) 76 | } 77 | 78 | variable "public_alb_target_groups" { 79 | type = map(object({ 80 | arn = string 81 | })) 82 | } -------------------------------------------------------------------------------- /modules/vpc/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | # VPC 6 | resource "aws_vpc" "vpc" { 7 | cidr_block = var.cidr 8 | enable_dns_hostnames = true 9 | enable_dns_support = true 10 | tags = { 11 | Name = var.app_name 12 | Env = var.env 13 | } 14 | } 15 | 16 | # Internet gateway 17 | resource "aws_internet_gateway" "igw" { 18 | vpc_id = aws_vpc.vpc.id 19 | tags = { 20 | Name = var.app_name 21 | Env = var.env 22 | } 23 | } 24 | 25 | # Private subnets 26 | resource "aws_subnet" "private_subnet" { 27 | vpc_id = aws_vpc.vpc.id 28 | count = length(var.private_subnets) 29 | cidr_block = element(var.private_subnets, count.index ) 30 | availability_zone = element(var.availability_zones, count.index ) 31 | 32 | tags = { 33 | Name = "${lower(var.app_name)}-private-subnet-${count.index}" 34 | Env = var.env 35 | } 36 | } 37 | 38 | # Public subnets 39 | resource "aws_subnet" "public_subnet" { 40 | vpc_id = aws_vpc.vpc.id 41 | count = length(var.public_subnets) 42 | cidr_block = element(var.public_subnets, count.index ) 43 | availability_zone = element(var.availability_zones, count.index ) 44 | map_public_ip_on_launch = true 45 | 46 | tags = { 47 | Name = "${lower(var.app_name)}-public-subnet-${count.index}" 48 | Env = var.env 49 | } 50 | } 51 | 52 | # Public route table 53 | resource "aws_route_table" "public_route_table" { 54 | vpc_id = aws_vpc.vpc.id 55 | tags = { 56 | Name = "${lower(var.app_name)}public-route-table" 57 | Env = var.env 58 | } 59 | } 60 | 61 | # Public route 62 | resource "aws_route" "public-route" { 63 | route_table_id = aws_route_table.public_route_table.id 64 | destination_cidr_block = "0.0.0.0/0" 65 | gateway_id = aws_internet_gateway.igw.id 66 | } 67 | 68 | # Route table association with public subnets 69 | resource "aws_route_table_association" "public-route-association" { 70 | count = length(var.public_subnets) 71 | subnet_id = element(aws_subnet.public_subnet.*.id, count.index ) 72 | route_table_id = aws_route_table.public_route_table.id 73 | } 74 | 75 | # Private route table 76 | resource "aws_route_table" "private_route_table" { 77 | vpc_id = aws_vpc.vpc.id 78 | tags = { 79 | Name = "${lower(var.app_name)}private-route-table" 80 | Env = var.env 81 | } 82 | } 83 | 84 | # Public route 85 | resource "aws_route" "private_route" { 86 | route_table_id = aws_route_table.private_route_table.id 87 | destination_cidr_block = "0.0.0.0/0" 88 | gateway_id = aws_nat_gateway.nat_gateway.id 89 | } 90 | 91 | # Route table association with private subnets 92 | resource "aws_route_table_association" "private_route_association" { 93 | count = length(var.private_subnets) 94 | subnet_id = element(aws_subnet.private_subnet.*.id, count.index ) 95 | route_table_id = aws_route_table.private_route_table.id 96 | } 97 | 98 | # Nat gateway 99 | resource "aws_nat_gateway" "nat_gateway" { 100 | allocation_id = aws_eip.eip.id 101 | subnet_id = element(aws_subnet.public_subnet.*.id, 0) 102 | depends_on = [aws_internet_gateway.igw] 103 | } 104 | 105 | # Elastic API for gateway 106 | resource "aws_eip" "eip" { 107 | vpc = true 108 | } -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # Application 3 | variable "account" { 4 | type = number 5 | description = "AWS account number" 6 | } 7 | 8 | variable "region" { 9 | type = string 10 | description = "region" 11 | } 12 | 13 | variable "app_name" { 14 | type = string 15 | description = "Application name" 16 | } 17 | 18 | variable "app_services" { 19 | type = list(string) 20 | description = "service name list" 21 | } 22 | 23 | variable "env" { 24 | type = string 25 | description = "Environment" 26 | } 27 | ######################################################################################################################## 28 | 29 | # VPC 30 | variable "cidr" { 31 | type = string 32 | description = "VPC CIDR" 33 | } 34 | 35 | variable "availability_zones" { 36 | type = list(string) 37 | description = "Availability zones that the services are running" 38 | } 39 | 40 | variable "private_subnets" { 41 | type = list(string) 42 | description = "Private subnets" 43 | } 44 | 45 | variable "public_subnets" { 46 | type = list(string) 47 | description = "Public subnets" 48 | } 49 | ######################################################################################################################## 50 | #ALB 51 | variable "internal_alb_config" { 52 | type = object({ 53 | name = string 54 | listeners = map(object({ 55 | listener_port = number 56 | listener_protocol = string 57 | })) 58 | ingress_rules = list(object({ 59 | from_port = number 60 | to_port = number 61 | protocol = string 62 | cidr_blocks = list(string) 63 | })) 64 | egress_rules = list(object({ 65 | from_port = number 66 | to_port = number 67 | protocol = string 68 | cidr_blocks = list(string) 69 | })) 70 | }) 71 | description = "Internal ALB configuration" 72 | } 73 | 74 | variable "internal_url_name" { 75 | type = string 76 | description = "Friendly url name for the internal load balancer DNS" 77 | } 78 | 79 | variable "public_alb_config" { 80 | type = object({ 81 | name = string 82 | listeners = map(object({ 83 | listener_port = number 84 | listener_protocol = string 85 | })) 86 | ingress_rules = list(object({ 87 | from_port = number 88 | to_port = number 89 | protocol = string 90 | cidr_blocks = list(string) 91 | })) 92 | egress_rules = list(object({ 93 | from_port = number 94 | to_port = number 95 | protocol = string 96 | cidr_blocks = list(string) 97 | })) 98 | }) 99 | description = "Public ALB configuration" 100 | } 101 | ######################################################################################################################## 102 | # ECS 103 | variable "microservice_config" { 104 | type = map(object({ 105 | name = string 106 | is_public = bool 107 | container_port = number 108 | host_port = number 109 | cpu = number 110 | memory = number 111 | desired_count = number 112 | 113 | alb_target_group = object({ 114 | port = number 115 | protocol = string 116 | path_pattern = list(string) 117 | health_check_path = string 118 | priority = number 119 | }) 120 | 121 | auto_scaling = object({ 122 | max_capacity = number 123 | min_capacity = number 124 | cpu = object({ 125 | target_value = number 126 | }) 127 | memory = object({ 128 | target_value = number 129 | }) 130 | }) 131 | })) 132 | description = "Microservice configuration" 133 | } 134 | ######################################################################################################################## -------------------------------------------------------------------------------- /terraform.tfvars: -------------------------------------------------------------------------------- 1 | ## Application configurations 2 | account = 000000 3 | region = "us-east-1" 4 | app_name = "ecs-demo" 5 | env = "dev" 6 | app_services = ["webapp", "customer", "transaction"] 7 | 8 | #VPC configurations 9 | cidr = "10.10.0.0/16" 10 | availability_zones = ["us-east-1a", "us-east-1b"] 11 | public_subnets = ["10.10.50.0/24", "10.10.51.0/24"] 12 | private_subnets = ["10.10.0.0/24", "10.10.1.0/24"] 13 | 14 | #Internal ALB configurations 15 | internal_alb_config = { 16 | name = "Internal-Alb" 17 | listeners = { 18 | "HTTP" = { 19 | listener_port = 80 20 | listener_protocol = "HTTP" 21 | 22 | } 23 | } 24 | 25 | ingress_rules = [ 26 | { 27 | from_port = 80 28 | to_port = 3000 29 | protocol = "tcp" 30 | cidr_blocks = ["10.10.0.0/16"] 31 | } 32 | ] 33 | 34 | egress_rules = [ 35 | { 36 | from_port = 0 37 | to_port = 0 38 | protocol = "-1" 39 | cidr_blocks = ["10.10.0.0/16"] 40 | } 41 | ] 42 | } 43 | 44 | #Friendly url name for internal load balancer DNS 45 | internal_url_name = "service.internal" 46 | 47 | #Public ALB configurations 48 | public_alb_config = { 49 | name = "Public-Alb" 50 | listeners = { 51 | "HTTP" = { 52 | listener_port = 80 53 | listener_protocol = "HTTP" 54 | 55 | } 56 | } 57 | 58 | ingress_rules = [ 59 | { 60 | from_port = 80 61 | to_port = 80 62 | protocol = "tcp" 63 | cidr_blocks = ["0.0.0.0/0"] 64 | } 65 | ] 66 | 67 | egress_rules = [ 68 | { 69 | from_port = 0 70 | to_port = 0 71 | protocol = "-1" 72 | cidr_blocks = ["0.0.0.0/0"] 73 | } 74 | ] 75 | } 76 | 77 | #Microservices 78 | microservice_config = { 79 | "WebApp" = { 80 | name = "WebApp" 81 | is_public = true 82 | container_port = 80 83 | host_port = 80 84 | cpu = 256 85 | memory = 512 86 | desired_count = 1 87 | alb_target_group = { 88 | port = 80 89 | protocol = "HTTP" 90 | path_pattern = ["/*"] 91 | health_check_path = "/health" 92 | priority = 1 93 | } 94 | auto_scaling = { 95 | max_capacity = 2 96 | min_capacity = 1 97 | cpu = { 98 | target_value = 75 99 | } 100 | memory = { 101 | target_value = 75 102 | } 103 | } 104 | }, 105 | "Customer" = { 106 | name = "Customer" 107 | is_public = false 108 | container_port = 3000 109 | host_port = 3000 110 | cpu = 256 111 | memory = 512 112 | desired_count = 1 113 | alb_target_group = { 114 | port = 3000 115 | protocol = "HTTP" 116 | path_pattern = ["/customer*"] 117 | health_check_path = "/health" 118 | priority = 1 119 | } 120 | auto_scaling = { 121 | max_capacity = 2 122 | min_capacity = 1 123 | cpu = { 124 | target_value = 75 125 | } 126 | memory = { 127 | target_value = 75 128 | } 129 | } 130 | }, 131 | "Transaction" = { 132 | name = "Transaction" 133 | is_public = false 134 | container_port = 3000 135 | host_port = 3000 136 | cpu = 256 137 | memory = 512 138 | desired_count = 1 139 | alb_target_group = { 140 | port = 3000 141 | protocol = "HTTP" 142 | path_pattern = ["/transaction*"] 143 | health_check_path = "/health" 144 | priority = 1 145 | } 146 | auto_scaling = { 147 | max_capacity = 2 148 | min_capacity = 1 149 | cpu = { 150 | target_value = 75 151 | } 152 | memory = { 153 | target_value = 75 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 3.0" 6 | } 7 | } 8 | 9 | backend "s3" { 10 | bucket = "du-terraform-state-bucket" 11 | key = "state/terraform_state.tfstate" 12 | region = "us-east-1" 13 | } 14 | } 15 | 16 | provider "aws" { 17 | region = "us-east-1" 18 | } 19 | 20 | locals { 21 | internal_alb_target_groups = {for service, config in var.microservice_config : service => config.alb_target_group if !config.is_public} 22 | public_alb_target_groups = {for service, config in var.microservice_config : service => config.alb_target_group if config.is_public} 23 | } 24 | 25 | module "iam" { 26 | source = "./modules/iam" 27 | app_name = var.app_name 28 | } 29 | 30 | module "vpc" { 31 | source = "./modules/vpc" 32 | app_name = var.app_name 33 | env = var.env 34 | cidr = var.cidr 35 | availability_zones = var.availability_zones 36 | public_subnets = var.public_subnets 37 | private_subnets = var.private_subnets 38 | } 39 | 40 | module "internal_alb_security_group" { 41 | source = "./modules/security-group" 42 | name = "${lower(var.app_name)}-internal-alb-sg" 43 | description = "${lower(var.app_name)}-internal-alb-sg" 44 | vpc_id = module.vpc.vpc_id 45 | ingress_rules = var.internal_alb_config.ingress_rules 46 | egress_rules = var.internal_alb_config.egress_rules 47 | } 48 | 49 | module "public_alb_security_group" { 50 | source = "./modules/security-group" 51 | name = "${lower(var.app_name)}-public-alb-sg" 52 | description = "${lower(var.app_name)}-public-alb-sg" 53 | vpc_id = module.vpc.vpc_id 54 | ingress_rules = var.public_alb_config.ingress_rules 55 | egress_rules = var.public_alb_config.egress_rules 56 | } 57 | 58 | module "internal_alb" { 59 | source = "./modules/alb" 60 | name = "${lower(var.app_name)}-internal-alb" 61 | subnets = module.vpc.private_subnets 62 | vpc_id = module.vpc.vpc_id 63 | target_groups = local.internal_alb_target_groups 64 | internal = true 65 | listener_port = 80 66 | listener_protocol = "HTTP" 67 | listeners = var.internal_alb_config.listeners 68 | security_groups = [module.internal_alb_security_group.security_group_id] 69 | } 70 | 71 | module "public_alb" { 72 | source = "./modules/alb" 73 | name = "${lower(var.app_name)}-public-alb" 74 | subnets = module.vpc.public_subnets 75 | vpc_id = module.vpc.vpc_id 76 | target_groups = local.public_alb_target_groups 77 | internal = false 78 | listener_port = 80 79 | listener_protocol = "HTTP" 80 | listeners = var.public_alb_config.listeners 81 | security_groups = [module.public_alb_security_group.security_group_id] 82 | } 83 | 84 | module "route53_private_zone" { 85 | source = "./modules/route53" 86 | internal_url_name = var.internal_url_name 87 | alb = module.internal_alb.internal_alb 88 | vpc_id = module.vpc.vpc_id 89 | } 90 | 91 | module "ecr" { 92 | source = "./modules/ecr" 93 | app_name = var.app_name 94 | ecr_repositories = var.app_services 95 | } 96 | 97 | module "ecs" { 98 | source = "./modules/ecs" 99 | app_name = var.app_name 100 | app_services = var.app_services 101 | account = var.account 102 | region = var.region 103 | service_config = var.microservice_config 104 | ecs_task_execution_role_arn = module.iam.ecs_task_execution_role_arn 105 | vpc_id = module.vpc.vpc_id 106 | private_subnets = module.vpc.private_subnets 107 | public_subnets = module.vpc.public_subnets 108 | public_alb_security_group = module.public_alb_security_group 109 | internal_alb_security_group = module.internal_alb_security_group 110 | internal_alb_target_groups = module.internal_alb.target_groups 111 | public_alb_target_groups = module.public_alb.target_groups 112 | } -------------------------------------------------------------------------------- /modules/ecs/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_cluster" "ecs_cluster" { 2 | name = lower("${var.app_name}-cluster") 3 | } 4 | 5 | resource "aws_cloudwatch_log_group" "ecs_cw_log_group" { 6 | for_each = toset(var.app_services) 7 | name = lower("${each.key}-logs") 8 | } 9 | 10 | #Create task definitions for app services 11 | resource "aws_ecs_task_definition" "ecs_task_definition" { 12 | for_each = var.service_config 13 | family = "${lower(var.app_name)}-${each.key}" 14 | execution_role_arn = var.ecs_task_execution_role_arn 15 | requires_compatibilities = ["FARGATE"] 16 | network_mode = "awsvpc" 17 | memory = each.value.memory 18 | cpu = each.value.cpu 19 | 20 | container_definitions = jsonencode([ 21 | { 22 | name = each.value.name 23 | image = "${var.account}.dkr.ecr.${var.region}.amazonaws.com/${lower(var.app_name)}-${lower(each.value.name)}:latest" 24 | cpu = each.value.cpu 25 | memory = each.value.memory 26 | essential = true 27 | portMappings = [ 28 | { 29 | containerPort = each.value.container_port 30 | hostPort : each.value.host_port 31 | } 32 | ] 33 | 34 | logConfiguration = { 35 | logDriver = "awslogs" 36 | options = { 37 | awslogs-group = "${lower(each.value["name"])}-logs" 38 | awslogs-region = var.region 39 | awslogs-stream-prefix = var.app_name 40 | } 41 | } 42 | } 43 | ]) 44 | } 45 | 46 | #Create services for app services 47 | resource "aws_ecs_service" "private_service" { 48 | for_each = var.service_config 49 | 50 | name = "${each.value.name}-Service" 51 | cluster = aws_ecs_cluster.ecs_cluster.id 52 | task_definition = aws_ecs_task_definition.ecs_task_definition[each.key].arn 53 | launch_type = "FARGATE" 54 | desired_count = each.value.desired_count 55 | 56 | network_configuration { 57 | subnets = each.value.is_public ==true ? var.public_subnets : var.private_subnets 58 | assign_public_ip = each.value.is_public ==true ? true : false 59 | security_groups = [ 60 | each.value.is_public == true ? aws_security_group.webapp_security_group.id : aws_security_group.service_security_group.id 61 | ] 62 | } 63 | 64 | load_balancer { 65 | target_group_arn = each.value.is_public==true ? var.public_alb_target_groups[each.key].arn : var.internal_alb_target_groups[each.key].arn 66 | container_name = each.value.name 67 | container_port = each.value.container_port 68 | } 69 | } 70 | 71 | resource "aws_appautoscaling_target" "service_autoscaling" { 72 | for_each = var.service_config 73 | max_capacity = each.value.auto_scaling.max_capacity 74 | min_capacity = each.value.auto_scaling.min_capacity 75 | resource_id = "service/${aws_ecs_cluster.ecs_cluster.name}/${aws_ecs_service.private_service[each.key].name}" 76 | scalable_dimension = "ecs:service:DesiredCount" 77 | service_namespace = "ecs" 78 | } 79 | 80 | resource "aws_appautoscaling_policy" "ecs_policy_memory" { 81 | for_each = var.service_config 82 | name = "${var.app_name}-memory-autoscaling" 83 | policy_type = "TargetTrackingScaling" 84 | resource_id = aws_appautoscaling_target.service_autoscaling[each.key].resource_id 85 | scalable_dimension = aws_appautoscaling_target.service_autoscaling[each.key].scalable_dimension 86 | service_namespace = aws_appautoscaling_target.service_autoscaling[each.key].service_namespace 87 | 88 | target_tracking_scaling_policy_configuration { 89 | predefined_metric_specification { 90 | predefined_metric_type = "ECSServiceAverageMemoryUtilization" 91 | } 92 | 93 | target_value = each.value.auto_scaling.memory.target_value 94 | } 95 | } 96 | 97 | resource "aws_appautoscaling_policy" "ecs_policy_cpu" { 98 | for_each = var.service_config 99 | name = "${var.app_name}-cpu-autoscaling" 100 | policy_type = "TargetTrackingScaling" 101 | resource_id = aws_appautoscaling_target.service_autoscaling[each.key].resource_id 102 | scalable_dimension = aws_appautoscaling_target.service_autoscaling[each.key].scalable_dimension 103 | service_namespace = aws_appautoscaling_target.service_autoscaling[each.key].service_namespace 104 | 105 | target_tracking_scaling_policy_configuration { 106 | predefined_metric_specification { 107 | predefined_metric_type = "ECSServiceAverageCPUUtilization" 108 | } 109 | 110 | target_value = each.value.auto_scaling.cpu.target_value 111 | } 112 | } 113 | 114 | resource "aws_security_group" "service_security_group" { 115 | vpc_id = var.vpc_id 116 | 117 | ingress { 118 | from_port = 0 119 | to_port = 0 120 | protocol = "-1" 121 | security_groups = [var.internal_alb_security_group.security_group_id] 122 | } 123 | 124 | egress { 125 | from_port = 0 126 | to_port = 0 127 | protocol = "-1" 128 | cidr_blocks = ["0.0.0.0/0"] 129 | } 130 | } 131 | 132 | resource "aws_security_group" "webapp_security_group" { 133 | vpc_id = var.vpc_id 134 | 135 | ingress { 136 | from_port = 0 137 | to_port = 0 138 | protocol = "-1" 139 | security_groups = [var.public_alb_security_group.security_group_id] 140 | } 141 | 142 | egress { 143 | from_port = 0 144 | to_port = 0 145 | protocol = "-1" 146 | cidr_blocks = ["0.0.0.0/0"] 147 | } 148 | } -------------------------------------------------------------------------------- /sample-ecs-services/transaction-service/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-app2", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.1", 23 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", 24 | "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", 25 | "requires": { 26 | "bytes": "3.1.1", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.8.1", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.9.6", 34 | "raw-body": "2.4.2", 35 | "type-is": "~1.6.18" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.1", 40 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", 41 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" 42 | }, 43 | "content-disposition": { 44 | "version": "0.5.4", 45 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 46 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 47 | "requires": { 48 | "safe-buffer": "5.2.1" 49 | } 50 | }, 51 | "content-type": { 52 | "version": "1.0.4", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 54 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 55 | }, 56 | "cookie": { 57 | "version": "0.4.1", 58 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 59 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 60 | }, 61 | "cookie-signature": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 64 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 65 | }, 66 | "debug": { 67 | "version": "2.6.9", 68 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 69 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 70 | "requires": { 71 | "ms": "2.0.0" 72 | } 73 | }, 74 | "depd": { 75 | "version": "1.1.2", 76 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 77 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 78 | }, 79 | "destroy": { 80 | "version": "1.0.4", 81 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 82 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 83 | }, 84 | "ee-first": { 85 | "version": "1.1.1", 86 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 87 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 88 | }, 89 | "encodeurl": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 92 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 93 | }, 94 | "escape-html": { 95 | "version": "1.0.3", 96 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 97 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 98 | }, 99 | "etag": { 100 | "version": "1.8.1", 101 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 102 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 103 | }, 104 | "express": { 105 | "version": "4.17.2", 106 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", 107 | "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", 108 | "requires": { 109 | "accepts": "~1.3.7", 110 | "array-flatten": "1.1.1", 111 | "body-parser": "1.19.1", 112 | "content-disposition": "0.5.4", 113 | "content-type": "~1.0.4", 114 | "cookie": "0.4.1", 115 | "cookie-signature": "1.0.6", 116 | "debug": "2.6.9", 117 | "depd": "~1.1.2", 118 | "encodeurl": "~1.0.2", 119 | "escape-html": "~1.0.3", 120 | "etag": "~1.8.1", 121 | "finalhandler": "~1.1.2", 122 | "fresh": "0.5.2", 123 | "merge-descriptors": "1.0.1", 124 | "methods": "~1.1.2", 125 | "on-finished": "~2.3.0", 126 | "parseurl": "~1.3.3", 127 | "path-to-regexp": "0.1.7", 128 | "proxy-addr": "~2.0.7", 129 | "qs": "6.9.6", 130 | "range-parser": "~1.2.1", 131 | "safe-buffer": "5.2.1", 132 | "send": "0.17.2", 133 | "serve-static": "1.14.2", 134 | "setprototypeof": "1.2.0", 135 | "statuses": "~1.5.0", 136 | "type-is": "~1.6.18", 137 | "utils-merge": "1.0.1", 138 | "vary": "~1.1.2" 139 | } 140 | }, 141 | "finalhandler": { 142 | "version": "1.1.2", 143 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 144 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 145 | "requires": { 146 | "debug": "2.6.9", 147 | "encodeurl": "~1.0.2", 148 | "escape-html": "~1.0.3", 149 | "on-finished": "~2.3.0", 150 | "parseurl": "~1.3.3", 151 | "statuses": "~1.5.0", 152 | "unpipe": "~1.0.0" 153 | } 154 | }, 155 | "forwarded": { 156 | "version": "0.2.0", 157 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 158 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 159 | }, 160 | "fresh": { 161 | "version": "0.5.2", 162 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 163 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 164 | }, 165 | "http-errors": { 166 | "version": "1.8.1", 167 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 168 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 169 | "requires": { 170 | "depd": "~1.1.2", 171 | "inherits": "2.0.4", 172 | "setprototypeof": "1.2.0", 173 | "statuses": ">= 1.5.0 < 2", 174 | "toidentifier": "1.0.1" 175 | } 176 | }, 177 | "iconv-lite": { 178 | "version": "0.4.24", 179 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 180 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 181 | "requires": { 182 | "safer-buffer": ">= 2.1.2 < 3" 183 | } 184 | }, 185 | "inherits": { 186 | "version": "2.0.4", 187 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 188 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 189 | }, 190 | "ipaddr.js": { 191 | "version": "1.9.1", 192 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 193 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 194 | }, 195 | "media-typer": { 196 | "version": "0.3.0", 197 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 198 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 199 | }, 200 | "merge-descriptors": { 201 | "version": "1.0.1", 202 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 203 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 204 | }, 205 | "methods": { 206 | "version": "1.1.2", 207 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 208 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 209 | }, 210 | "mime": { 211 | "version": "1.6.0", 212 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 213 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 214 | }, 215 | "mime-db": { 216 | "version": "1.51.0", 217 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 218 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 219 | }, 220 | "mime-types": { 221 | "version": "2.1.34", 222 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 223 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 224 | "requires": { 225 | "mime-db": "1.51.0" 226 | } 227 | }, 228 | "ms": { 229 | "version": "2.0.0", 230 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 231 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 232 | }, 233 | "negotiator": { 234 | "version": "0.6.2", 235 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 236 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 237 | }, 238 | "on-finished": { 239 | "version": "2.3.0", 240 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 241 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 242 | "requires": { 243 | "ee-first": "1.1.1" 244 | } 245 | }, 246 | "parseurl": { 247 | "version": "1.3.3", 248 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 249 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 250 | }, 251 | "path-to-regexp": { 252 | "version": "0.1.7", 253 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 254 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 255 | }, 256 | "proxy-addr": { 257 | "version": "2.0.7", 258 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 259 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 260 | "requires": { 261 | "forwarded": "0.2.0", 262 | "ipaddr.js": "1.9.1" 263 | } 264 | }, 265 | "qs": { 266 | "version": "6.9.6", 267 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", 268 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" 269 | }, 270 | "range-parser": { 271 | "version": "1.2.1", 272 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 273 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 274 | }, 275 | "raw-body": { 276 | "version": "2.4.2", 277 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", 278 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", 279 | "requires": { 280 | "bytes": "3.1.1", 281 | "http-errors": "1.8.1", 282 | "iconv-lite": "0.4.24", 283 | "unpipe": "1.0.0" 284 | } 285 | }, 286 | "safe-buffer": { 287 | "version": "5.2.1", 288 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 289 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 290 | }, 291 | "safer-buffer": { 292 | "version": "2.1.2", 293 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 294 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 295 | }, 296 | "send": { 297 | "version": "0.17.2", 298 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 299 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 300 | "requires": { 301 | "debug": "2.6.9", 302 | "depd": "~1.1.2", 303 | "destroy": "~1.0.4", 304 | "encodeurl": "~1.0.2", 305 | "escape-html": "~1.0.3", 306 | "etag": "~1.8.1", 307 | "fresh": "0.5.2", 308 | "http-errors": "1.8.1", 309 | "mime": "1.6.0", 310 | "ms": "2.1.3", 311 | "on-finished": "~2.3.0", 312 | "range-parser": "~1.2.1", 313 | "statuses": "~1.5.0" 314 | }, 315 | "dependencies": { 316 | "ms": { 317 | "version": "2.1.3", 318 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 319 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 320 | } 321 | } 322 | }, 323 | "serve-static": { 324 | "version": "1.14.2", 325 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 326 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 327 | "requires": { 328 | "encodeurl": "~1.0.2", 329 | "escape-html": "~1.0.3", 330 | "parseurl": "~1.3.3", 331 | "send": "0.17.2" 332 | } 333 | }, 334 | "setprototypeof": { 335 | "version": "1.2.0", 336 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 337 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 338 | }, 339 | "statuses": { 340 | "version": "1.5.0", 341 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 342 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 343 | }, 344 | "toidentifier": { 345 | "version": "1.0.1", 346 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 347 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 348 | }, 349 | "type-is": { 350 | "version": "1.6.18", 351 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 352 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 353 | "requires": { 354 | "media-typer": "0.3.0", 355 | "mime-types": "~2.1.24" 356 | } 357 | }, 358 | "unpipe": { 359 | "version": "1.0.0", 360 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 361 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 362 | }, 363 | "utils-merge": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 366 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 367 | }, 368 | "vary": { 369 | "version": "1.1.2", 370 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 371 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /sample-ecs-services/frontend-app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webapp", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.1", 23 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", 24 | "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", 25 | "requires": { 26 | "bytes": "3.1.1", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.8.1", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.9.6", 34 | "raw-body": "2.4.2", 35 | "type-is": "~1.6.18" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.1", 40 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", 41 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" 42 | }, 43 | "content-disposition": { 44 | "version": "0.5.4", 45 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 46 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 47 | "requires": { 48 | "safe-buffer": "5.2.1" 49 | } 50 | }, 51 | "content-type": { 52 | "version": "1.0.4", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 54 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 55 | }, 56 | "cookie": { 57 | "version": "0.4.1", 58 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 59 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 60 | }, 61 | "cookie-signature": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 64 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 65 | }, 66 | "data-uri-to-buffer": { 67 | "version": "4.0.0", 68 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", 69 | "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" 70 | }, 71 | "debug": { 72 | "version": "2.6.9", 73 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 74 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 75 | "requires": { 76 | "ms": "2.0.0" 77 | } 78 | }, 79 | "depd": { 80 | "version": "1.1.2", 81 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 82 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 83 | }, 84 | "destroy": { 85 | "version": "1.0.4", 86 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 87 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 88 | }, 89 | "ee-first": { 90 | "version": "1.1.1", 91 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 92 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 93 | }, 94 | "encodeurl": { 95 | "version": "1.0.2", 96 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 97 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 98 | }, 99 | "escape-html": { 100 | "version": "1.0.3", 101 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 102 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 103 | }, 104 | "etag": { 105 | "version": "1.8.1", 106 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 107 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 108 | }, 109 | "express": { 110 | "version": "4.17.2", 111 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", 112 | "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", 113 | "requires": { 114 | "accepts": "~1.3.7", 115 | "array-flatten": "1.1.1", 116 | "body-parser": "1.19.1", 117 | "content-disposition": "0.5.4", 118 | "content-type": "~1.0.4", 119 | "cookie": "0.4.1", 120 | "cookie-signature": "1.0.6", 121 | "debug": "2.6.9", 122 | "depd": "~1.1.2", 123 | "encodeurl": "~1.0.2", 124 | "escape-html": "~1.0.3", 125 | "etag": "~1.8.1", 126 | "finalhandler": "~1.1.2", 127 | "fresh": "0.5.2", 128 | "merge-descriptors": "1.0.1", 129 | "methods": "~1.1.2", 130 | "on-finished": "~2.3.0", 131 | "parseurl": "~1.3.3", 132 | "path-to-regexp": "0.1.7", 133 | "proxy-addr": "~2.0.7", 134 | "qs": "6.9.6", 135 | "range-parser": "~1.2.1", 136 | "safe-buffer": "5.2.1", 137 | "send": "0.17.2", 138 | "serve-static": "1.14.2", 139 | "setprototypeof": "1.2.0", 140 | "statuses": "~1.5.0", 141 | "type-is": "~1.6.18", 142 | "utils-merge": "1.0.1", 143 | "vary": "~1.1.2" 144 | } 145 | }, 146 | "fetch-blob": { 147 | "version": "3.1.4", 148 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", 149 | "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", 150 | "requires": { 151 | "node-domexception": "^1.0.0", 152 | "web-streams-polyfill": "^3.0.3" 153 | } 154 | }, 155 | "finalhandler": { 156 | "version": "1.1.2", 157 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 158 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 159 | "requires": { 160 | "debug": "2.6.9", 161 | "encodeurl": "~1.0.2", 162 | "escape-html": "~1.0.3", 163 | "on-finished": "~2.3.0", 164 | "parseurl": "~1.3.3", 165 | "statuses": "~1.5.0", 166 | "unpipe": "~1.0.0" 167 | } 168 | }, 169 | "formdata-polyfill": { 170 | "version": "4.0.10", 171 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 172 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 173 | "requires": { 174 | "fetch-blob": "^3.1.2" 175 | } 176 | }, 177 | "forwarded": { 178 | "version": "0.2.0", 179 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 180 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 181 | }, 182 | "fresh": { 183 | "version": "0.5.2", 184 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 185 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 186 | }, 187 | "http-errors": { 188 | "version": "1.8.1", 189 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 190 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 191 | "requires": { 192 | "depd": "~1.1.2", 193 | "inherits": "2.0.4", 194 | "setprototypeof": "1.2.0", 195 | "statuses": ">= 1.5.0 < 2", 196 | "toidentifier": "1.0.1" 197 | } 198 | }, 199 | "iconv-lite": { 200 | "version": "0.4.24", 201 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 202 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 203 | "requires": { 204 | "safer-buffer": ">= 2.1.2 < 3" 205 | } 206 | }, 207 | "inherits": { 208 | "version": "2.0.4", 209 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 210 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 211 | }, 212 | "ipaddr.js": { 213 | "version": "1.9.1", 214 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 215 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 216 | }, 217 | "media-typer": { 218 | "version": "0.3.0", 219 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 220 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 221 | }, 222 | "merge-descriptors": { 223 | "version": "1.0.1", 224 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 225 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 226 | }, 227 | "methods": { 228 | "version": "1.1.2", 229 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 230 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 231 | }, 232 | "mime": { 233 | "version": "1.6.0", 234 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 235 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 236 | }, 237 | "mime-db": { 238 | "version": "1.51.0", 239 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 240 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 241 | }, 242 | "mime-types": { 243 | "version": "2.1.34", 244 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 245 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 246 | "requires": { 247 | "mime-db": "1.51.0" 248 | } 249 | }, 250 | "ms": { 251 | "version": "2.0.0", 252 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 253 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 254 | }, 255 | "negotiator": { 256 | "version": "0.6.2", 257 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 258 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 259 | }, 260 | "node-domexception": { 261 | "version": "1.0.0", 262 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 263 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" 264 | }, 265 | "node-fetch": { 266 | "version": "3.2.0", 267 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.0.tgz", 268 | "integrity": "sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==", 269 | "requires": { 270 | "data-uri-to-buffer": "^4.0.0", 271 | "fetch-blob": "^3.1.4", 272 | "formdata-polyfill": "^4.0.10" 273 | } 274 | }, 275 | "on-finished": { 276 | "version": "2.3.0", 277 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 278 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 279 | "requires": { 280 | "ee-first": "1.1.1" 281 | } 282 | }, 283 | "parseurl": { 284 | "version": "1.3.3", 285 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 286 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 287 | }, 288 | "path-to-regexp": { 289 | "version": "0.1.7", 290 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 291 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 292 | }, 293 | "proxy-addr": { 294 | "version": "2.0.7", 295 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 296 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 297 | "requires": { 298 | "forwarded": "0.2.0", 299 | "ipaddr.js": "1.9.1" 300 | } 301 | }, 302 | "qs": { 303 | "version": "6.9.6", 304 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", 305 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" 306 | }, 307 | "range-parser": { 308 | "version": "1.2.1", 309 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 310 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 311 | }, 312 | "raw-body": { 313 | "version": "2.4.2", 314 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", 315 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", 316 | "requires": { 317 | "bytes": "3.1.1", 318 | "http-errors": "1.8.1", 319 | "iconv-lite": "0.4.24", 320 | "unpipe": "1.0.0" 321 | } 322 | }, 323 | "safe-buffer": { 324 | "version": "5.2.1", 325 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 326 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 327 | }, 328 | "safer-buffer": { 329 | "version": "2.1.2", 330 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 331 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 332 | }, 333 | "send": { 334 | "version": "0.17.2", 335 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 336 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 337 | "requires": { 338 | "debug": "2.6.9", 339 | "depd": "~1.1.2", 340 | "destroy": "~1.0.4", 341 | "encodeurl": "~1.0.2", 342 | "escape-html": "~1.0.3", 343 | "etag": "~1.8.1", 344 | "fresh": "0.5.2", 345 | "http-errors": "1.8.1", 346 | "mime": "1.6.0", 347 | "ms": "2.1.3", 348 | "on-finished": "~2.3.0", 349 | "range-parser": "~1.2.1", 350 | "statuses": "~1.5.0" 351 | }, 352 | "dependencies": { 353 | "ms": { 354 | "version": "2.1.3", 355 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 356 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 357 | } 358 | } 359 | }, 360 | "serve-static": { 361 | "version": "1.14.2", 362 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 363 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 364 | "requires": { 365 | "encodeurl": "~1.0.2", 366 | "escape-html": "~1.0.3", 367 | "parseurl": "~1.3.3", 368 | "send": "0.17.2" 369 | } 370 | }, 371 | "setprototypeof": { 372 | "version": "1.2.0", 373 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 374 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 375 | }, 376 | "statuses": { 377 | "version": "1.5.0", 378 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 379 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 380 | }, 381 | "toidentifier": { 382 | "version": "1.0.1", 383 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 384 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 385 | }, 386 | "type-is": { 387 | "version": "1.6.18", 388 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 389 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 390 | "requires": { 391 | "media-typer": "0.3.0", 392 | "mime-types": "~2.1.24" 393 | } 394 | }, 395 | "unpipe": { 396 | "version": "1.0.0", 397 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 398 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 399 | }, 400 | "utils-merge": { 401 | "version": "1.0.1", 402 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 403 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 404 | }, 405 | "vary": { 406 | "version": "1.1.2", 407 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 408 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 409 | }, 410 | "web-streams-polyfill": { 411 | "version": "3.2.0", 412 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", 413 | "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" 414 | } 415 | } 416 | } 417 | --------------------------------------------------------------------------------