`. The new module
16 | can be found under the `modules` directory.
17 |
18 | The make target creates a complete skeleton module for you. You can cd into
19 | the module directory and run `make test`. The example should run without error.
20 | Once you have verified that the skeleton project works you can begin coding
21 | it's functionality.
22 |
23 | #### Module Layout
24 |
25 | The template module is based on
26 | [this](https://www.terraform.io/language/modules/develop/structure) article.
27 | There are a few modifications (`test/` and `versions.tf`). This layout is the
28 | minimum... you may need to add to it. For example you might create a
29 | `modules/` directory to contain embedded submodules.
30 |
31 | ```text
32 | terraform-my-module
33 | ├── examples < - Example usage of the module goes here.
34 | │ └── simple All modules should include at least one.
35 | │ └── main.tf
36 | ├── main.tf < - The main functionality of the module goes here.
37 | ├── outputs.tf < - Module outputs.
38 | ├── test < - Terratest tests go here.
39 | │ ├── go.mod
40 | │ ├── go.sum
41 | │ └── integration_test.go
42 | ├── variables.tf < - Module variables go here.
43 | └── versions.tf < - Module requirements go here.
44 | ```
45 |
46 | ### Documentation
47 |
48 | This repo uses `terraform-docs` via `pre-commit` to automatically generate
49 | documentation of variables, outputs, and requirements. Additionally you should
50 | provide guidance by editing the header section of the module's `main.tf` file.
51 | The skeleton includes a header comment that looks like this:
52 |
53 | ```text
54 | /**
55 | *
5 | */
6 |
7 | locals {
8 | name = var.name
9 | ami_id = var.ami_id != null ? var.ami_id : jsondecode(data.aws_ssm_parameter.ami_id[0].value)["image_id"]
10 | user_data_b64 = var.user_data == null ? null : base64encode(var.user_data)
11 | subnet_ids = var.subnet_ids
12 | cloudwatch_config = var.cloudwatch_config != null ? var.cloudwatch_config : templatefile("${path.module}/cloudwatch-config.tftpl", { autoscaling_group_name = aws_autoscaling_group.this.name })
13 | }
14 |
15 | data "aws_ssm_parameter" "ami_id" {
16 | count = var.ami_id == null ? 1 : 0
17 | name = var.ami_ssm_parameter
18 | }
19 |
20 | data "aws_iam_policy_document" "this" {
21 | statement {
22 | actions = ["sts:AssumeRole"]
23 |
24 | principals {
25 | type = "Service"
26 | identifiers = ["ec2.amazonaws.com"]
27 | }
28 | }
29 | }
30 |
31 | resource "aws_iam_role" "this" {
32 | name = "${local.name}-InstanceProfile"
33 | path = "/system/"
34 | assume_role_policy = data.aws_iam_policy_document.this.json
35 | }
36 |
37 | resource "aws_iam_role_policy_attachment" "this" {
38 | for_each = toset(var.instance_role_policies)
39 | role = aws_iam_role.this.name
40 | policy_arn = each.value
41 | }
42 |
43 | resource "aws_iam_instance_profile" "this" {
44 | role = aws_iam_role.this.name
45 | }
46 |
47 | resource "aws_launch_template" "this" {
48 | #checkov:skip=CKV_AWS_46:N/A
49 | image_id = local.ami_id
50 | instance_type = var.instance_type
51 | name = local.name
52 | user_data = local.user_data_b64
53 |
54 | vpc_security_group_ids = var.security_group_ids
55 |
56 | iam_instance_profile {
57 | arn = aws_iam_instance_profile.this.arn
58 | }
59 |
60 | block_device_mappings {
61 | device_name = "/dev/xvda"
62 | ebs {
63 | volume_size = var.root_volume_size
64 | }
65 | }
66 |
67 | metadata_options {
68 | http_endpoint = "enabled"
69 | http_tokens = "required"
70 | }
71 | }
72 |
73 | resource "aws_ssm_association" "configure_aws_packages" {
74 | for_each = toset(var.aws_packages)
75 | name = "AWS-ConfigureAWSPackage"
76 | schedule_expression = "cron(0 2 ? * SUN *)"
77 | targets {
78 | key = "tag:aws:autoscaling:groupName"
79 | values = [
80 | aws_autoscaling_group.this.name
81 | ]
82 | }
83 | parameters = {
84 | "action" = "Install"
85 | "name" = each.value
86 | }
87 | }
88 |
89 | resource "aws_ssm_parameter" "cloudwatch_config" {
90 | #checkov:skip=CKV_AWS_337: TODO: Ensure SSM parameters are using KMS CMK.
91 | name = "/${local.name}/cloudwatch-config"
92 | type = "SecureString"
93 | value = local.cloudwatch_config
94 | }
95 |
96 | resource "aws_ssm_association" "cloudwatch_manage_agent" {
97 | # TODO: This tries to apply before the Cloudwatch Agent is installed
98 | # so it fails. It works at the next 30 minute interval but that
99 | # leaves a 30 minute gap every time a new instance launches.
100 | count = contains(var.aws_packages, "AmazonCloudWatchAgent") ? 1 : 0
101 | name = "AmazonCloudWatch-ManageAgent"
102 | schedule_expression = "rate(30 minutes)"
103 | targets {
104 | key = "tag:aws:autoscaling:groupName"
105 | values = [
106 | aws_autoscaling_group.this.name
107 | ]
108 | }
109 | parameters = {
110 | "optionalConfigurationLocation" = aws_ssm_parameter.cloudwatch_config.name
111 | }
112 | }
113 |
114 | resource "aws_autoscaling_group" "this" {
115 | name = local.name
116 | max_size = var.max_size
117 | min_size = var.min_size
118 | vpc_zone_identifier = local.subnet_ids
119 | target_group_arns = var.target_group_arns
120 | protect_from_scale_in = false
121 |
122 | launch_template {
123 | id = aws_launch_template.this.id
124 | version = aws_launch_template.this.latest_version
125 | }
126 |
127 | dynamic "tag" {
128 | for_each = var.instance_tags
129 | content {
130 | key = tag.key
131 | value = tag.value
132 | propagate_at_launch = true
133 | }
134 | }
135 |
136 | tag {
137 | key = "Name"
138 | value = local.name
139 | propagate_at_launch = true
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/modules/bananalab-platform/README.md:
--------------------------------------------------------------------------------
1 | # bananalab-platform
2 |
3 |
4 |
5 |
8 |
9 | ## Example
10 |
11 | ```hcl
12 | /**
13 | * Examples should illustrate typical use cases.
14 | * For multiple examples each should have its own directory.
15 | *
16 | * > Running module examples uses a local state file.
17 | * > If you delete the .terraform directory the resources
18 | * > will be orphaned.
19 | */
20 |
21 | locals {
22 | name = terraform.workspace
23 | domain_name = "bananalab.dev"
24 | availability_zones = slice(data.aws_availability_zones.available.names, 0, 2)
25 | asg_min_instances = length(local.availability_zones)
26 | }
27 |
28 | provider "aws" {
29 | default_tags {
30 | tags = {
31 | terraformed = "true"
32 | example = "simple"
33 | module = "bananalab-platform"
34 | }
35 | }
36 | }
37 |
38 | data "aws_availability_zones" "available" {
39 | state = "available"
40 | }
41 |
42 | module "this" {
43 | source = "../../"
44 | name = local.name
45 | availability_zones = local.availability_zones
46 | domain_name = local.domain_name
47 | asg_min_instances = local.asg_min_instances
48 | boot_script = templatefile("${path.module}/boot_script.sh.tftpl", {})
49 | }
50 |
51 | output "result" {
52 | description = <<-EOT
53 | The result of the module.
54 | EOT
55 | value = module.this.result
56 | }
57 | ```
58 |
59 |
60 | ## Modules
61 |
62 | | Name | Source | Version |
63 | |------|--------|---------|
64 | | [alb](#module\_alb) | ../aws-https-alb | n/a |
65 | | [asg](#module\_asg) | ../aws-ec2-asg | n/a |
66 | | [ecs](#module\_ecs) | ../aws-ecs-cluster | n/a |
67 | | [log\_bucket](#module\_log\_bucket) | ../aws-s3-bucket | n/a |
68 | | [vpc](#module\_vpc) | ../aws-vpc | n/a |
69 |
70 | ## Providers
71 |
72 | | Name | Version |
73 | |------|---------|
74 | | [aws](#provider\_aws) | ~>5.0 |
75 |
76 | ## Requirements
77 |
78 | | Name | Version |
79 | |------|---------|
80 | | [terraform](#requirement\_terraform) | ~>1.5.0 |
81 | | [aws](#requirement\_aws) | ~>5.0 |
82 |
83 | ## Resources
84 |
85 | | Name | Type |
86 | |------|------|
87 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
88 |
89 | ## Inputs
90 |
91 | | Name | Description | Type | Default | Required |
92 | |------|-------------|------|---------|:--------:|
93 | | [availability\_zones](#input\_availability\_zones) | Availability zones to create resources in. | `list(string)` | n/a | yes |
94 | | [domain\_name](#input\_domain\_name) | Name of existing Route53 domain to use. | `string` | n/a | yes |
95 | | [name](#input\_name) | The name of the resources created by this module.
This value is used as the basis for naming various resources. | `string` | n/a | yes |
96 | | [asg\_instance\_type](#input\_asg\_instance\_type) | EC2 instance type to use in the autoscaling group. | `string` | `"m6i.4xlarge"` | no |
97 | | [asg\_max\_instances](#input\_asg\_max\_instances) | Maximum number of instances in the autoscaling group. If this is less than
`asg_min_instances` then `asg_min_instances` will be used. | `number` | `1` | no |
98 | | [asg\_min\_instances](#input\_asg\_min\_instances) | Minimum number of instances in the autoscaling group. | `number` | `1` | no |
99 | | [asg\_root\_volume\_size](#input\_asg\_root\_volume\_size) | Size in GB of root volume. | `number` | `1000` | no |
100 | | [boot\_script](#input\_boot\_script) | Content of shell script to execute at EC2 boot.
Do not include plain text secrets. | `string` | `null` | no |
101 | | [ecs\_agent\_config](#input\_ecs\_agent\_config) | Key / Value pairs of ECS Agent environment variables.
See: https://github.com/aws/amazon-ecs-agent/blob/master/README.md#environment-variables
For options.
ECS\_CLUSTER is set by default. | `map(string)` | `{}` | no |
102 |
103 | ## Outputs
104 |
105 | | Name | Description |
106 | |------|-------------|
107 | | [result](#output\_result) | The result of the module. |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/modules/aws-vpc/main.tf:
--------------------------------------------------------------------------------
1 | /**
2 | *
5 | */
6 |
7 |
8 | locals {
9 | new_bits = var.subnets_netmask_length - var.vpc_netmask_length
10 | private_subnet_names = [for az in var.availability_zones : "${az}_private"]
11 | public_subnet_names = [for az in var.availability_zones : "${az}_public"]
12 | public_subnets = { for network in module.subnet_addrs.networks : trimsuffix(network.name, "_public") => network.cidr_block if endswith(network.name, "public") }
13 | private_subnets = { for network in module.subnet_addrs.networks : trimsuffix(network.name, "_private") => network.cidr_block if endswith(network.name, "private") }
14 | networks = [for subnet in concat(local.private_subnet_names, local.public_subnet_names) : { "new_bits" : local.new_bits, "name" : subnet }]
15 | base_cidr_block = var.ipam_pool_id == null ? "${var.vpc_ip_address}/${var.vpc_netmask_length}" : aws_vpc.this.cidr_block
16 | }
17 |
18 | module "subnet_addrs" {
19 | #checkov:skip=CKV_TF_1:Not applicable
20 | source = "hashicorp/subnets/cidr"
21 | version = "~> 1.0"
22 | base_cidr_block = local.base_cidr_block
23 | networks = local.networks
24 | }
25 |
26 | resource "aws_vpc" "this" {
27 | #checkov:skip=CKV2_AWS_11:TODO: Support flow logging
28 | #checkov:skip=CKV2_AWS_12:TODO: Restrict default SG
29 | cidr_block = var.ipam_pool_id == null ? "${var.vpc_ip_address}/${var.vpc_netmask_length}" : null
30 | ipv4_ipam_pool_id = var.ipam_pool_id == null ? null : var.ipam_pool_id
31 | ipv4_netmask_length = var.ipam_pool_id == null ? null : var.vpc_netmask_length
32 | tags = {
33 | Name = var.name
34 | }
35 | }
36 |
37 | resource "aws_internet_gateway" "this" {
38 | count = var.create_public_subnets ? 1 : 0
39 | vpc_id = aws_vpc.this.id
40 | }
41 |
42 | resource "aws_subnet" "public" {
43 | for_each = var.create_public_subnets ? local.public_subnets : {}
44 | vpc_id = aws_vpc.this.id
45 | cidr_block = each.value
46 | availability_zone = each.key
47 | #checkov:skip=CKV_AWS_130:Public subnet
48 | map_public_ip_on_launch = true
49 | tags = {
50 | Name = "${each.key}-public"
51 | Tier = "Public"
52 | }
53 | }
54 |
55 | resource "aws_route_table" "public" {
56 | count = var.create_public_subnets ? 1 : 0
57 | vpc_id = aws_vpc.this.id
58 | tags = {
59 | Tier = "Public"
60 | }
61 | }
62 |
63 | resource "aws_route" "public" {
64 | count = var.create_public_subnets ? 1 : 0
65 | route_table_id = aws_route_table.public[0].id
66 | destination_cidr_block = "0.0.0.0/0"
67 | gateway_id = aws_internet_gateway.this[0].id
68 | }
69 |
70 | resource "aws_route_table_association" "public" {
71 | for_each = var.create_public_subnets ? aws_subnet.public : {}
72 | subnet_id = each.value.id
73 | route_table_id = aws_route_table.public[0].id
74 | }
75 |
76 | resource "aws_nat_gateway" "this" {
77 | for_each = var.create_nat_gateways ? aws_subnet.public : {}
78 | allocation_id = aws_eip.nat[each.key].id
79 | subnet_id = each.value.id
80 | depends_on = [aws_internet_gateway.this]
81 | }
82 |
83 | resource "aws_eip" "nat" {
84 | # checkov:skip=CKV2_AWS_19: Attached using allocation_id in NAT GWs
85 | for_each = var.create_nat_gateways ? aws_subnet.public : {}
86 | domain = "vpc"
87 | }
88 |
89 | # Private subnet config
90 |
91 | resource "aws_subnet" "private" {
92 | for_each = local.private_subnets
93 | vpc_id = aws_vpc.this.id
94 | cidr_block = each.value
95 | availability_zone = each.key
96 | tags = {
97 | Name = "${each.key}-private"
98 | Tier = "Private"
99 | }
100 | }
101 |
102 | resource "aws_route_table" "private" {
103 | for_each = aws_subnet.private
104 | vpc_id = aws_vpc.this.id
105 | tags = {
106 | Tier = "Private"
107 | }
108 | }
109 |
110 | resource "aws_route" "nat_gw" {
111 | for_each = var.create_nat_gateways ? aws_route_table.private : {}
112 | route_table_id = each.value.id
113 | destination_cidr_block = "0.0.0.0/0"
114 | nat_gateway_id = aws_nat_gateway.this[each.key].id
115 | }
116 |
117 | resource "aws_route_table_association" "nat_gw" {
118 | for_each = var.create_nat_gateways ? aws_route.nat_gw : {}
119 | subnet_id = aws_subnet.private[each.key].id
120 | route_table_id = aws_route_table.private[each.key].id
121 | }
122 |
123 | resource "aws_ec2_transit_gateway_vpc_attachment" "this" {
124 | count = var.attach_transit_gateway ? 1 : 0
125 | subnet_ids = [for subnet in aws_subnet.private : subnet.id]
126 | transit_gateway_id = var.transit_gateway_id
127 | vpc_id = aws_vpc.this.id
128 | }
129 |
130 | resource "aws_route" "transit_gw" {
131 | for_each = var.attach_transit_gateway ? aws_route_table.private : {}
132 | route_table_id = each.value.id
133 | destination_cidr_block = "0.0.0.0/0"
134 | transit_gateway_id = var.transit_gateway_id
135 | }
136 |
137 | resource "aws_route_table_association" "transit_gw" {
138 | for_each = var.attach_transit_gateway ? aws_route.transit_gw : {}
139 | subnet_id = aws_subnet.private[each.key].id
140 | route_table_id = aws_route_table.private[each.key].id
141 | }
142 |
--------------------------------------------------------------------------------
/modules/aws-https-alb/main.tf:
--------------------------------------------------------------------------------
1 | /**
2 | *
5 | */
6 |
7 | locals {
8 | fqdn = "${var.application_host}.${var.domain_name}"
9 | }
10 |
11 | resource "aws_security_group" "alb" {
12 | # checkov:skip=CKV_AWS_260: N/A
13 | name = "${var.name}-allow_web_ports"
14 | description = "Allow web ports"
15 | vpc_id = var.vpc_id
16 | }
17 |
18 | resource "aws_vpc_security_group_ingress_rule" "http" {
19 | # checkov:skip=CKV_AWS_260: N/A
20 | security_group_id = aws_security_group.alb.id
21 | description = "HTTP from public"
22 | from_port = 80
23 | to_port = 80
24 | ip_protocol = "tcp"
25 | cidr_ipv4 = "0.0.0.0/0"
26 | }
27 |
28 | resource "aws_vpc_security_group_ingress_rule" "https" {
29 | security_group_id = aws_security_group.alb.id
30 | description = "HTTPS from public"
31 | from_port = 443
32 | to_port = 443
33 | ip_protocol = "tcp"
34 | cidr_ipv4 = "0.0.0.0/0"
35 | }
36 |
37 | resource "aws_vpc_security_group_ingress_rule" "intragroup" {
38 | security_group_id = aws_security_group.alb.id
39 | description = "Allow intragroup"
40 | ip_protocol = "-1"
41 | referenced_security_group_id = aws_security_group.alb.id
42 | }
43 |
44 | resource "aws_vpc_security_group_egress_rule" "allow_all" {
45 | security_group_id = aws_security_group.alb.id
46 | description = "Allow egress"
47 | ip_protocol = "-1"
48 | cidr_ipv4 = "0.0.0.0/0"
49 | }
50 |
51 | resource "aws_lb" "this" {
52 | # checkov:skip=CKV_AWS_150: N/A
53 | # checkov:skip=CKV2_AWS_28: Deletion protection isn't desired here
54 | name = var.name
55 | internal = false
56 | load_balancer_type = "application"
57 | security_groups = [aws_security_group.alb.id]
58 | subnets = var.subnet_ids
59 | drop_invalid_header_fields = true
60 | enable_deletion_protection = false
61 | idle_timeout = var.idle_timeout
62 |
63 | access_logs {
64 | bucket = var.log_bucket
65 | prefix = "${var.name}/alb"
66 | enabled = true
67 | }
68 | }
69 |
70 | resource "aws_lb_listener" "this" {
71 | load_balancer_arn = aws_lb.this.arn
72 | port = "80"
73 | protocol = "HTTP"
74 |
75 | default_action {
76 | type = "redirect"
77 |
78 | redirect {
79 | port = "443"
80 | protocol = "HTTPS"
81 | status_code = "HTTP_301"
82 | }
83 | }
84 | }
85 |
86 | resource "aws_lb_listener" "https" {
87 | load_balancer_arn = aws_lb.this.id
88 | port = 443
89 | protocol = "HTTPS"
90 | certificate_arn = aws_acm_certificate_validation.this.certificate_arn
91 | ssl_policy = "ELBSecurityPolicy-FS-1-2-Res-2020-10"
92 |
93 | default_action {
94 | type = "fixed-response"
95 |
96 | fixed_response {
97 | content_type = "text/html"
98 | message_body = "Not Found."
99 | status_code = "404"
100 | }
101 | }
102 |
103 | lifecycle {
104 | replace_triggered_by = [aws_acm_certificate.this]
105 | }
106 | }
107 |
108 | data "aws_route53_zone" "this" {
109 | name = var.domain_name
110 | }
111 |
112 | resource "aws_route53_record" "alb" {
113 | zone_id = data.aws_route53_zone.this.zone_id
114 | name = local.fqdn
115 | type = "A"
116 |
117 | alias {
118 | name = aws_lb.this.dns_name
119 | zone_id = aws_lb.this.zone_id
120 | evaluate_target_health = true
121 | }
122 | }
123 |
124 | resource "aws_acm_certificate" "this" {
125 | domain_name = local.fqdn
126 | validation_method = "DNS"
127 |
128 | lifecycle {
129 | create_before_destroy = true
130 | }
131 | }
132 |
133 | resource "aws_route53_record" "validation" {
134 | for_each = {
135 | for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
136 | name = dvo.resource_record_name
137 | record = dvo.resource_record_value
138 | type = dvo.resource_record_type
139 | }
140 | }
141 |
142 | allow_overwrite = true
143 | name = each.value.name
144 | records = [each.value.record]
145 | ttl = 60
146 | type = each.value.type
147 | zone_id = data.aws_route53_zone.this.zone_id
148 | }
149 |
150 | resource "aws_acm_certificate_validation" "this" {
151 | certificate_arn = aws_acm_certificate.this.arn
152 | validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
153 | }
154 | /*
155 | module "log_bucket" {
156 | source = "../aws-s3-bucket"
157 | bucket_prefix = "${var.name}-lb-logs"
158 | enable_replication = false
159 | logging_enabled = false
160 | }
161 | */
162 | data "aws_iam_policy_document" "log_access" {
163 | statement {
164 | sid = ""
165 | effect = "Allow"
166 | resources = ["arn:aws:s3:::${var.log_bucket}/*"]
167 | actions = ["s3:PutObject"]
168 |
169 | principals {
170 | type = "AWS"
171 | # TODO: Allow other regions
172 | # See: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/enable-access-logging.html#attach-bucket-policy
173 | identifiers = [
174 | "arn:aws:iam::027434742980:root", # us-west-1
175 | "arn:aws:iam::797873946194:root" # us-west-2
176 | ]
177 | }
178 | }
179 | }
180 |
181 | resource "aws_s3_bucket_policy" "this" {
182 | bucket = var.log_bucket
183 | policy = data.aws_iam_policy_document.log_access.json
184 | }
185 |
--------------------------------------------------------------------------------
/modules/aws-vpc/README.md:
--------------------------------------------------------------------------------
1 | # aws-vpc
2 |
3 |
4 |
5 |
8 |
9 | ## Example
10 |
11 | ```hcl
12 | provider "aws" {
13 | default_tags {
14 | tags = {
15 | Terraformed = true
16 | Environment = "test"
17 | Name = "vpc-module-test"
18 | }
19 | }
20 | }
21 |
22 | data "aws_availability_zones" "available" {
23 | state = "available"
24 | }
25 |
26 | locals {
27 | availability_zones = slice(data.aws_availability_zones.available.names, 0, 2)
28 | }
29 |
30 | module "this" {
31 | source = "../../"
32 | name = "example"
33 | availability_zones = local.availability_zones
34 | vpc_ip_address = "10.11.0.0"
35 | vpc_netmask_length = 16
36 | subnets_netmask_length = 20
37 | create_public_subnets = true
38 | create_nat_gateways = true
39 | }
40 | ```
41 |
42 |
43 | ## Modules
44 |
45 | | Name | Source | Version |
46 | |------|--------|---------|
47 | | [subnet\_addrs](#module\_subnet\_addrs) | hashicorp/subnets/cidr | ~> 1.0 |
48 |
49 | ## Providers
50 |
51 | | Name | Version |
52 | |------|---------|
53 | | [aws](#provider\_aws) | ~>5.0 |
54 |
55 | ## Requirements
56 |
57 | | Name | Version |
58 | |------|---------|
59 | | [terraform](#requirement\_terraform) | ~>1.5 |
60 | | [aws](#requirement\_aws) | ~>5.0 |
61 |
62 | ## Resources
63 |
64 | | Name | Type |
65 | |------|------|
66 | | [aws_ec2_transit_gateway_vpc_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_vpc_attachment) | resource |
67 | | [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
68 | | [aws_internet_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource |
69 | | [aws_nat_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource |
70 | | [aws_route.nat_gw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource |
71 | | [aws_route.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource |
72 | | [aws_route.transit_gw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource |
73 | | [aws_route_table.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource |
74 | | [aws_route_table.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource |
75 | | [aws_route_table_association.nat_gw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
76 | | [aws_route_table_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
77 | | [aws_route_table_association.transit_gw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
78 | | [aws_subnet.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource |
79 | | [aws_subnet.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource |
80 | | [aws_vpc.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource |
81 |
82 | ## Inputs
83 |
84 | | Name | Description | Type | Default | Required |
85 | |------|-------------|------|---------|:--------:|
86 | | [availability\_zones](#input\_availability\_zones) | Availability zones to create resources in. | `list(string)` | n/a | yes |
87 | | [name](#input\_name) | Name of the VPC. Used to name various other resources. | `string` | n/a | yes |
88 | | [subnets\_netmask\_length](#input\_subnets\_netmask\_length) | The netmask length of the IPv4 CIDR you want to allocate to subnets.
Must be greater than `vpc_netmask_lengthh` | `string` | n/a | yes |
89 | | [vpc\_netmask\_length](#input\_vpc\_netmask\_length) | The netmask length of the IPv4 CIDR to allocate to this VPC. | `number` | n/a | yes |
90 | | [attach\_transit\_gateway](#input\_attach\_transit\_gateway) | Toggle Transit Gateway attachment.
Requires `transit_gateway_id`. | `bool` | `false` | no |
91 | | [create\_nat\_gateways](#input\_create\_nat\_gateways) | Toggle nat gateway creation.
Conflicts with `transit_gateway_id`.
Requires `create_public_subnets`. | `bool` | `false` | no |
92 | | [create\_public\_subnets](#input\_create\_public\_subnets) | Toggle public subnet creation. | `bool` | `false` | no |
93 | | [ipam\_pool\_id](#input\_ipam\_pool\_id) | The ID of an IPv4 IPAM pool you want to use for allocating this VPC's CIDR.
Conflicts with `vpc_ip_address`. | `string` | `null` | no |
94 | | [transit\_gateway\_id](#input\_transit\_gateway\_id) | Transit gateway to connect to.
Requires `attach_transit_gateway`.
Conflicts with `create_nat_gateways`. | `string` | `null` | no |
95 | | [vpc\_ip\_address](#input\_vpc\_ip\_address) | Base IP address block for VPC. Combined with `vpc_netmask_length` to form
CIDR for the VPC.
Conflicts with `ipam_pool_id`. | `string` | `null` | no |
96 |
97 | ## Outputs
98 |
99 | | Name | Description |
100 | |------|-------------|
101 | | [result](#output\_result) | The result of the module. |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/modules/aws-ec2-asg/README.md:
--------------------------------------------------------------------------------
1 | # aws-ec2-asg
2 |
3 |
4 |
5 |
8 |
9 | ## Example
10 |
11 | ```hcl
12 | provider "aws" {
13 | default_tags {
14 | tags = {
15 | Terraformed = true
16 | Environment = "test"
17 | Name = "asg-module-test"
18 | }
19 | }
20 | }
21 |
22 | data "aws_subnets" "private" {
23 | tags = {
24 | Tier = "Private"
25 | }
26 | }
27 |
28 | module "this" {
29 | source = "../../"
30 | name = "asg-module-example"
31 | user_data = templatefile("user_data.sh", {})
32 | subnet_ids = data.aws_subnets.private.ids
33 | ami_ssm_parameter = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended"
34 | }
35 |
36 | output "result" {
37 | description = <<-EOT
38 | The result of the module.
39 | EOT
40 | value = module.this.result
41 | }
42 | ```
43 |
44 |
45 | ## Modules
46 |
47 | No modules.
48 |
49 | ## Providers
50 |
51 | | Name | Version |
52 | |------|---------|
53 | | [aws](#provider\_aws) | ~>5.0 |
54 |
55 | ## Requirements
56 |
57 | | Name | Version |
58 | |------|---------|
59 | | [terraform](#requirement\_terraform) | ~>1.5 |
60 | | [aws](#requirement\_aws) | ~>5.0 |
61 | | [random](#requirement\_random) | ~>3.0 |
62 |
63 | ## Resources
64 |
65 | | Name | Type |
66 | |------|------|
67 | | [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource |
68 | | [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
69 | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
70 | | [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
71 | | [aws_launch_template.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource |
72 | | [aws_ssm_association.cloudwatch_manage_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_association) | resource |
73 | | [aws_ssm_association.configure_aws_packages](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_association) | resource |
74 | | [aws_ssm_parameter.cloudwatch_config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
75 | | [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
76 | | [aws_ssm_parameter.ami_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
77 |
78 | ## Inputs
79 |
80 | | Name | Description | Type | Default | Required |
81 | |------|-------------|------|---------|:--------:|
82 | | [name](#input\_name) | The name of the ASG.
This value is used as the basis for naming various other resources.
If this value is not specified a random name will be generated. | `string` | n/a | yes |
83 | | [subnet\_ids](#input\_subnet\_ids) | Subnets to deploy instances in. | `list(string)` | n/a | yes |
84 | | [ami\_id](#input\_ami\_id) | AMI ID for instances created by the ASG.
Conflicts with `ami_ssm_parameter`. | `string` | `null` | no |
85 | | [ami\_ssm\_parameter](#input\_ami\_ssm\_parameter) | Name of SSM parameter that contains the AMI ID to use.
ex. "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended"
Conflicts with `ami_id`. | `string` | `null` | no |
86 | | [aws\_packages](#input\_aws\_packages) | List of AWS packages to deploy via SSM. | `list(string)` | [
"AWSCodeDeployAgent",
"AmazonCloudWatchAgent"
]
| no |
87 | | [cloudwatch\_config](#input\_cloudwatch\_config) | Cloudwatch config file contents. | `string` | `null` | no |
88 | | [instance\_role\_policies](#input\_instance\_role\_policies) | IAM Policy ARNs to attach to the instance profile. | `list(string)` | [
"arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeploy",
"arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
"arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
"arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
]
| no |
89 | | [instance\_tags](#input\_instance\_tags) | Tags to apply to instances.
Use the provider `default_tags` feature for more consistent tagging. | `map(string)` | `{}` | no |
90 | | [instance\_type](#input\_instance\_type) | Instance type | `string` | `"t2.micro"` | no |
91 | | [max\_size](#input\_max\_size) | Maximum number of instances to create. | `number` | `1` | no |
92 | | [min\_size](#input\_min\_size) | Minimum number of instances to create. | `number` | `1` | no |
93 | | [root\_volume\_size](#input\_root\_volume\_size) | Size in GB of root volume size. | `number` | `100` | no |
94 | | [security\_group\_ids](#input\_security\_group\_ids) | Security groups to attach to the managed instances. | `list(string)` | `null` | no |
95 | | [target\_group\_arns](#input\_target\_group\_arns) | Load balancer targets to register with. | `list(string)` | `null` | no |
96 | | [user\_data](#input\_user\_data) | Instance User Data.
See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html | `string` | `null` | no |
97 |
98 | ## Outputs
99 |
100 | | Name | Description |
101 | |------|-------------|
102 | | [result](#output\_result) | The result of the module. |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/modules/aws-https-alb/README.md:
--------------------------------------------------------------------------------
1 | # aws-https-alb
2 |
3 |
4 |
5 |
8 |
9 | ## Example
10 |
11 | ```hcl
12 | variable "name" {
13 | default = "aws-https-module-example"
14 | }
15 |
16 | variable "domain_name" {
17 | default = "bananalab.dev"
18 | }
19 |
20 | data "aws_vpc" "default" {
21 | default = true
22 | }
23 |
24 | data "aws_subnets" "default" {
25 | filter {
26 | name = "vpc-id"
27 | values = [data.aws_vpc.default.id]
28 | }
29 | filter {
30 | name = "default-for-az"
31 | values = ["true"]
32 | }
33 | }
34 |
35 | module "this" {
36 | source = "../../"
37 | name = var.name
38 | vpc_id = data.aws_vpc.default.id
39 | subnet_ids = data.aws_subnets.default.ids
40 | application_host = var.name
41 | domain_name = var.domain_name
42 | }
43 |
44 | output "result" {
45 | description = <<-EOT
46 | The result of the module.
47 | EOT
48 | value = module.this.result
49 | }
50 | ```
51 |
52 |
53 | ## Modules
54 |
55 | No modules.
56 |
57 | ## Providers
58 |
59 | | Name | Version |
60 | |------|---------|
61 | | [aws](#provider\_aws) | ~>5.0 |
62 |
63 | ## Requirements
64 |
65 | | Name | Version |
66 | |------|---------|
67 | | [terraform](#requirement\_terraform) | ~>1.5.0 |
68 | | [aws](#requirement\_aws) | ~>5.0 |
69 | | [random](#requirement\_random) | ~>3.0 |
70 |
71 | ## Resources
72 |
73 | | Name | Type |
74 | |------|------|
75 | | [aws_acm_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
76 | | [aws_acm_certificate_validation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
77 | | [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
78 | | [aws_lb.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource |
79 | | [aws_lb_listener.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
80 | | [aws_lb_listener.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
81 | | [aws_route53_record.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
82 | | [aws_route53_record.validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
83 | | [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
84 | | [aws_security_group.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
85 | | [aws_vpc_security_group_egress_rule.allow_all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
86 | | [aws_vpc_security_group_ingress_rule.http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
87 | | [aws_vpc_security_group_ingress_rule.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
88 | | [aws_vpc_security_group_ingress_rule.intragroup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
89 | | [aws_wafv2_web_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl) | resource |
90 | | [aws_wafv2_web_acl_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource |
91 | | [aws_wafv2_web_acl_logging_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration) | resource |
92 | | [aws_iam_policy_document.log_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
93 | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
94 |
95 | ## Inputs
96 |
97 | | Name | Description | Type | Default | Required |
98 | |------|-------------|------|---------|:--------:|
99 | | [application\_host](#input\_application\_host) | Hostname where the application will be reachable. | `string` | n/a | yes |
100 | | [domain\_name](#input\_domain\_name) | DNS domain.
This will be combined with application\_host to form the fqdn.
This should already exist as a route53 hosted domain. | `string` | n/a | yes |
101 | | [log\_bucket](#input\_log\_bucket) | S3 bucket to store logs. | `string` | n/a | yes |
102 | | [name](#input\_name) | The name of the ALB.
This value is used as the basis for naming various other resources. | `string` | n/a | yes |
103 | | [subnet\_ids](#input\_subnet\_ids) | Subnets to deploy Load Balancer in. | `list(string)` | n/a | yes |
104 | | [vpc\_id](#input\_vpc\_id) | ID of VPC. | `string` | n/a | yes |
105 | | [enable\_waf](#input\_enable\_waf) | Enable or disable WAF. | `bool` | `true` | no |
106 | | [idle\_timeout](#input\_idle\_timeout) | The time in seconds that the connection is allowed to be idle. | `number` | `600` | no |
107 | | [waf\_log\_retention\_days](#input\_waf\_log\_retention\_days) | Number of days to retain WAF logs | `number` | `90` | no |
108 | | [waf\_managed\_rules](#input\_waf\_managed\_rules) | List of WAF managed rules. | map(object({
rule_action_overrides = optional(map(string), {})
})) | {
"AWSManagedRulesAmazonIpReputationList": {},
"AWSManagedRulesAnonymousIpList": {},
"AWSManagedRulesCommonRuleSet": {
"rule_action_overrides": {
"SizeRestrictions_BODY": "allow"
}
},
"AWSManagedRulesKnownBadInputsRuleSet": {},
"AWSManagedRulesLinuxRuleSet": {},
"AWSManagedRulesPHPRuleSet": {},
"AWSManagedRulesSQLiRuleSet": {}
} | no |
109 |
110 | ## Outputs
111 |
112 | | Name | Description |
113 | |------|-------------|
114 | | [result](#output\_result) | The result of the module. |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/modules/aws-s3-bucket/README.md:
--------------------------------------------------------------------------------
1 |
2 | # aws-s3-bucket
3 |
4 |
5 |
6 |
9 | L1 Module to create an S3 bucket.
10 |
11 | ## Example
12 |
13 | ```hcl
14 | # tflint-ignore: all
15 |
16 | variable "bucket" { default = null }
17 | variable "bucket_prefix" { default = null }
18 | variable "force_destroy" { default = false }
19 | variable "object_lock_enabled" { default = false }
20 | variable "tags" { default = null }
21 | variable "block_public_acls" { default = true }
22 | variable "block_public_policy" { default = true }
23 | variable "ignore_public_acls" { default = true }
24 | variable "restrict_public_buckets" { default = true }
25 | variable "versioning_enabled" { default = true }
26 | variable "expected_bucket_owner" { default = null }
27 | variable "logging_enabled" { default = false }
28 | variable "logging_target_bucket" { default = null }
29 | variable "logging_target_prefix" { default = null }
30 | variable "enable_server_side_encryption" { default = true }
31 | variable "kms_master_key_id" { default = null }
32 | variable "enable_replication" { default = false }
33 | variable "replication_role" { default = null }
34 | variable "replication_target_bucket" { default = null }
35 |
36 |
37 |
38 | module "this" {
39 | source = "../../"
40 | bucket = var.bucket
41 | bucket_prefix = var.bucket_prefix
42 | force_destroy = var.force_destroy
43 | object_lock_enabled = var.object_lock_enabled
44 | tags = var.tags
45 | block_public_acls = var.block_public_acls
46 | block_public_policy = var.block_public_policy
47 | ignore_public_acls = var.ignore_public_acls
48 | versioning_enabled = var.versioning_enabled
49 | logging_enabled = var.logging_enabled
50 | logging_target_bucket = var.logging_target_bucket
51 | logging_target_prefix = var.logging_target_prefix
52 | expected_bucket_owner = var.expected_bucket_owner
53 | restrict_public_buckets = var.restrict_public_buckets
54 | enable_server_side_encryption = var.enable_server_side_encryption
55 | kms_master_key_id = var.kms_master_key_id
56 | enable_replication = var.enable_replication
57 | replication_role = var.replication_role
58 | replication_target_bucket = var.replication_target_bucket
59 | }
60 |
61 | output "result" {
62 | description = <<-EOT
63 | The result of the module.
64 | EOT
65 | value = module.this.result
66 | }
67 | ```
68 |
69 |
70 | ## Modules
71 |
72 | No modules.
73 |
74 | ## Providers
75 |
76 | | Name | Version |
77 | |------|---------|
78 | | [aws](#provider\_aws) | ~>5.0 |
79 |
80 | ## Requirements
81 |
82 | | Name | Version |
83 | |------|---------|
84 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
85 | | [aws](#requirement\_aws) | ~>5.0 |
86 |
87 | ## Resources
88 |
89 | | Name | Type |
90 | |------|------|
91 | | [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
92 | | [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
93 | | [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
94 | | [aws_s3_bucket_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
95 | | [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
96 | | [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
97 |
98 | ## Inputs
99 |
100 | | Name | Description | Type | Default | Required |
101 | |------|-------------|------|---------|:--------:|
102 | | [block\_public\_acls](#input\_block\_public\_acls) | Block public access to the bucket | `bool` | `true` | no |
103 | | [block\_public\_policy](#input\_block\_public\_policy) | Block public policy access to the bucket | `bool` | `true` | no |
104 | | [bucket](#input\_bucket) | The name of the S3 bucket to create | `string` | `null` | no |
105 | | [bucket\_prefix](#input\_bucket\_prefix) | The prefix to use for the S3 bucket name | `string` | `null` | no |
106 | | [enable\_replication](#input\_enable\_replication) | Enable replication for the bucket | `bool` | `true` | no |
107 | | [enable\_server\_side\_encryption](#input\_enable\_server\_side\_encryption) | Enable server side encryption for the bucket | `bool` | `true` | no |
108 | | [expected\_bucket\_owner](#input\_expected\_bucket\_owner) | The expected owner of the bucket | `string` | `null` | no |
109 | | [force\_destroy](#input\_force\_destroy) | Force destroy the bucket if it exists | `bool` | `false` | no |
110 | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Ignore public acls for the bucket | `bool` | `true` | no |
111 | | [kms\_master\_key\_id](#input\_kms\_master\_key\_id) | The KMS key id to use for encryption | `string` | `null` | no |
112 | | [logging\_enabled](#input\_logging\_enabled) | Enable logging for the bucket | `bool` | `true` | no |
113 | | [logging\_target\_bucket](#input\_logging\_target\_bucket) | The target bucket for the logging configuration | `string` | `null` | no |
114 | | [logging\_target\_prefix](#input\_logging\_target\_prefix) | The target prefix for the logging configuration | `string` | `null` | no |
115 | | [object\_lock\_enabled](#input\_object\_lock\_enabled) | Enable object lock for the bucket | `bool` | `false` | no |
116 | | [replication\_role](#input\_replication\_role) | The role ARN to use for replication | `string` | `null` | no |
117 | | [replication\_target\_bucket](#input\_replication\_target\_bucket) | The target bucket to use for replication | `string` | `null` | no |
118 | | [replication\_token](#input\_replication\_token) | The token to use for replication | `string` | `null` | no |
119 | | [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Restrict public access to the bucket | `bool` | `true` | no |
120 | | [tags](#input\_tags) | Tags to apply to the bucket | `map(any)` | `null` | no |
121 | | [versioning\_enabled](#input\_versioning\_enabled) | Enable versioning for the bucket | `bool` | `true` | no |
122 |
123 | ## Outputs
124 |
125 | | Name | Description |
126 | |------|-------------|
127 | | [result](#output\_result) | The result of the module. |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/modules/bananalab-ecs-service/main.tf:
--------------------------------------------------------------------------------
1 | /**
2 | *
5 | */
6 |
7 | locals {
8 | fqdn = var.fqdn
9 | domain_parts = split(".", local.fqdn)
10 | domain = length(local.domain_parts) == 2 ? var.fqdn : join(".", slice(split(".", local.fqdn), 1, length(split(".", local.fqdn))))
11 | vpc_id = data.aws_subnet.this.vpc_id
12 | }
13 |
14 | resource "aws_ecs_task_definition" "this" {
15 | family = var.name
16 | container_definitions = var.container_definitions
17 | task_role_arn = var.task_role_arn
18 | execution_role_arn = aws_iam_role.ecs_execution.arn
19 | network_mode = "awsvpc"
20 | cpu = var.cpu
21 | memory = var.memory
22 | requires_compatibilities = var.requires_compatibilities
23 | tags = var.task_tags
24 | dynamic "placement_constraints" {
25 | for_each = var.placement_constraints
26 | content {
27 | type = placement_constraints.value.type
28 | expression = lookup(placement_constraints.value.expression, null)
29 | }
30 | }
31 |
32 | dynamic "volume" {
33 | # TODO: Support EFS volumes
34 | for_each = var.volumes
35 | content {
36 | name = volume.key
37 | docker_volume_configuration {
38 | scope = lookup(volume.value.docker_volume_configuration, "scope", null)
39 | autoprovision = lookup(volume.value.docker_volume_configuration, "autoprovision", null)
40 | driver = lookup(volume.value.docker_volume_configuration, "driver", null)
41 | driver_opts = lookup(volume.value.docker_volume_configuration, "driver_opts", null)
42 | labels = lookup(volume.value.docker_volume_configuration, "labels", null)
43 | }
44 | }
45 | }
46 | }
47 |
48 | resource "aws_ecs_service" "this" {
49 | name = var.name
50 | cluster = var.ecs_cluster_id
51 | task_definition = aws_ecs_task_definition.this.arn
52 | desired_count = var.desired_count
53 | capacity_provider_strategy {
54 | base = 1
55 | capacity_provider = var.capacity_provider
56 | weight = 100
57 | }
58 | network_configuration {
59 | subnets = var.subnet_ids
60 | assign_public_ip = false
61 | security_groups = [aws_security_group.service.id]
62 | }
63 |
64 | dynamic "load_balancer" {
65 | for_each = var.load_balancer_targets
66 | content {
67 | target_group_arn = aws_lb_target_group.this[load_balancer.key].arn
68 | container_port = lookup(load_balancer.value, "port", 80)
69 | container_name = load_balancer.key
70 | }
71 | }
72 | }
73 |
74 | data "aws_subnet" "this" {
75 | id = var.subnet_ids[0]
76 | }
77 |
78 | resource "aws_lb_target_group" "this" {
79 | # checkov:skip=CKV_AWS_261
80 | for_each = var.load_balancer_targets
81 | port = lookup(each.value, "port", 80)
82 | protocol = lookup(each.value, "protocol", "HTTP")
83 | target_type = "ip"
84 | vpc_id = local.vpc_id
85 | }
86 |
87 | data "aws_lb_listener" "selected443" {
88 | load_balancer_arn = var.load_balancer_arn
89 | port = 443
90 | }
91 |
92 | data "aws_route53_zone" "selected" {
93 | name = local.domain
94 | private_zone = false
95 | }
96 |
97 | data "aws_lb" "this" {
98 | arn = var.load_balancer_arn
99 | }
100 |
101 | resource "aws_acm_certificate" "this" {
102 | domain_name = local.fqdn
103 | validation_method = "DNS"
104 | subject_alternative_names = keys(var.url_rewrites)
105 |
106 | lifecycle {
107 | create_before_destroy = true
108 | }
109 | }
110 |
111 | resource "aws_route53_record" "validation" {
112 | for_each = {
113 | for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
114 | name = dvo.resource_record_name
115 | record = dvo.resource_record_value
116 | type = dvo.resource_record_type
117 | }
118 | }
119 |
120 | allow_overwrite = true
121 | name = each.value.name
122 | records = [each.value.record]
123 | ttl = 60
124 | type = each.value.type
125 | zone_id = data.aws_route53_zone.selected.zone_id
126 | }
127 |
128 | resource "aws_acm_certificate_validation" "this" {
129 | certificate_arn = aws_acm_certificate.this.arn
130 | validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
131 | }
132 |
133 | resource "aws_lb_listener_certificate" "this" {
134 | listener_arn = data.aws_lb_listener.selected443.arn
135 | certificate_arn = aws_acm_certificate_validation.this.certificate_arn
136 | }
137 |
138 | resource "aws_route53_record" "this" {
139 | for_each = toset(concat([var.fqdn], keys(var.url_rewrites)))
140 | zone_id = data.aws_route53_zone.selected.zone_id
141 | name = each.value
142 | type = "A"
143 | alias {
144 | name = data.aws_lb.this.dns_name
145 | zone_id = data.aws_lb.this.zone_id
146 | evaluate_target_health = true
147 | }
148 | weighted_routing_policy {
149 | weight = var.dns_routing_weight
150 | }
151 | set_identifier = "${var.name}-${each.value}"
152 | }
153 |
154 | resource "aws_lb_listener_rule" "this" {
155 | for_each = aws_lb_target_group.this
156 | listener_arn = data.aws_lb_listener.selected443.arn
157 |
158 | action {
159 | type = "forward"
160 | target_group_arn = each.value.arn
161 | }
162 |
163 | condition {
164 | host_header {
165 | values = [var.fqdn]
166 | }
167 | }
168 | }
169 |
170 | resource "aws_lb_listener_rule" "rewrite" {
171 | for_each = var.url_rewrites
172 | listener_arn = data.aws_lb_listener.selected443.arn
173 |
174 | action {
175 | type = "redirect"
176 | redirect {
177 | host = lookup(each.value, "host", var.fqdn)
178 | path = lookup(each.value, "path", "")
179 | query = lookup(each.value, "query", "")
180 | status_code = "HTTP_301"
181 | }
182 | }
183 |
184 | condition {
185 | host_header {
186 | values = [each.key]
187 | }
188 | }
189 | }
190 |
191 | resource "aws_security_group" "service" {
192 | name = "${var.name}_allow_lb_traffic"
193 | description = "Allow LB traffic"
194 | vpc_id = local.vpc_id
195 |
196 | dynamic "ingress" {
197 | for_each = var.load_balancer_targets
198 | content {
199 | description = "Allow service traffic"
200 | from_port = lookup(ingress.value, "port", 80)
201 | to_port = lookup(ingress.value, "port", 80)
202 | protocol = "tcp"
203 | security_groups = data.aws_lb.this.security_groups
204 | }
205 | }
206 |
207 | egress {
208 | description = "Allow all."
209 | from_port = 0
210 | to_port = 0
211 | protocol = "-1"
212 | cidr_blocks = ["0.0.0.0/0"]
213 | ipv6_cidr_blocks = ["::/0"]
214 | }
215 | }
216 |
217 | data "aws_iam_policy_document" "ecs_execution_policy" {
218 | # checkov:skip=CKV_AWS_356
219 | # checkov:skip=CKV_AWS_111
220 | statement {
221 | effect = "Allow"
222 | actions = [
223 | "ecr:GetAuthorizationToken",
224 | "ecr:BatchCheckLayerAvailability",
225 | "ecr:GetDownloadUrlForLayer",
226 | "ecr:BatchGetImage",
227 | "logs:CreateLogStream",
228 | "logs:PutLogEvents",
229 | "logs:CreateLogGroup"
230 | ]
231 |
232 | resources = [
233 | "*"
234 | ]
235 | }
236 | }
237 |
238 | resource "aws_iam_policy" "ecs_execution" {
239 | name = "${var.name}_default_ecs_execution_policy"
240 | path = "/"
241 | policy = data.aws_iam_policy_document.ecs_execution_policy.json
242 | }
243 |
244 | data "aws_iam_policy_document" "ecs_execution_role_policy" {
245 | statement {
246 | actions = ["sts:AssumeRole"]
247 |
248 | principals {
249 | type = "Service"
250 | identifiers = ["ecs-tasks.amazonaws.com"]
251 | }
252 | }
253 | }
254 |
255 | resource "aws_iam_role" "ecs_execution" {
256 | name = "${var.name}EcsExecutionRole"
257 | path = "/system/"
258 | assume_role_policy = data.aws_iam_policy_document.ecs_execution_role_policy.json
259 | }
260 |
261 | resource "aws_iam_role_policy_attachment" "ecs_task" {
262 | role = aws_iam_role.ecs_execution.name
263 | policy_arn = aws_iam_policy.ecs_execution.arn
264 | }
265 |
--------------------------------------------------------------------------------
/modules/bananalab-ecs-service/README.md:
--------------------------------------------------------------------------------
1 | # bananalab-ecs-service
2 |
3 |
4 |
5 |
8 |
9 | ## Example
10 |
11 | ```hcl
12 | /**
13 | * Examples should illustrate typical use cases.
14 | * For multiple examples each should have its own directory.
15 | *
16 | * > Running module examples uses a local state file.
17 | * > If you delete the .terraform directory the resources
18 | * > will be orphaned.
19 | */
20 |
21 | provider "aws" {
22 | default_tags {
23 | tags = {
24 | terraformed = "true"
25 | example = "simple"
26 | module = local.name
27 | }
28 | }
29 | }
30 |
31 | data "aws_vpc" "this" {
32 | tags = {
33 | Name = local.platform_id
34 | }
35 | }
36 |
37 | data "aws_subnets" "private" {
38 | filter {
39 | name = "vpc-id"
40 | values = [data.aws_vpc.this.id]
41 | }
42 |
43 | tags = {
44 | Tier = "Private"
45 | }
46 | }
47 |
48 | data "aws_ecs_cluster" "this" {
49 | cluster_name = local.platform_id
50 | }
51 |
52 | data "aws_lb" "this" {
53 | name = local.platform_id
54 | }
55 |
56 | locals {
57 | name = "bananalab-ecs-service-example"
58 | domain_name = "bananalab.dev"
59 | platform_id = "default"
60 | container_definitions = file("container-definitions.json")
61 | volumes = {}
62 |
63 | load_balancer_targets = {
64 | "nginx" = { port = 80 }
65 | }
66 |
67 | url_rewrites = {}
68 |
69 | }
70 |
71 | module "this" {
72 | source = "../../"
73 | name = local.name
74 | fqdn = "${local.name}.${local.domain_name}"
75 | ecs_cluster_id = data.aws_ecs_cluster.this.id
76 | capacity_provider = local.platform_id
77 | subnet_ids = data.aws_subnets.private.ids
78 | load_balancer_arn = data.aws_lb.this.arn
79 | container_definitions = local.container_definitions
80 | volumes = local.volumes
81 | load_balancer_targets = local.load_balancer_targets
82 | url_rewrites = local.url_rewrites
83 | }
84 |
85 | output "result" {
86 | description = <<-EOT
87 | The result of the module.
88 | EOT
89 | value = module.this.result
90 | }
91 | ```
92 |
93 |
94 | ## Modules
95 |
96 | No modules.
97 |
98 | ## Providers
99 |
100 | | Name | Version |
101 | |------|---------|
102 | | [aws](#provider\_aws) | ~>5.0 |
103 |
104 | ## Requirements
105 |
106 | | Name | Version |
107 | |------|---------|
108 | | [terraform](#requirement\_terraform) | ~>1.5 |
109 | | [aws](#requirement\_aws) | ~>5.0 |
110 |
111 | ## Resources
112 |
113 | | Name | Type |
114 | |------|------|
115 | | [aws_acm_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
116 | | [aws_acm_certificate_validation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
117 | | [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
118 | | [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
119 | | [aws_iam_policy.ecs_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
120 | | [aws_iam_role.ecs_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
121 | | [aws_iam_role_policy_attachment.ecs_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
122 | | [aws_lb_listener_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_certificate) | resource |
123 | | [aws_lb_listener_rule.rewrite](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource |
124 | | [aws_lb_listener_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource |
125 | | [aws_lb_target_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource |
126 | | [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
127 | | [aws_route53_record.validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
128 | | [aws_security_group.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
129 | | [aws_iam_policy_document.ecs_execution_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
130 | | [aws_iam_policy_document.ecs_execution_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
131 | | [aws_lb.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lb) | data source |
132 | | [aws_lb_listener.selected443](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lb_listener) | data source |
133 | | [aws_route53_zone.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
134 | | [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source |
135 |
136 | ## Inputs
137 |
138 | | Name | Description | Type | Default | Required |
139 | |------|-------------|------|---------|:--------:|
140 | | [capacity\_provider](#input\_capacity\_provider) | ECS Capacity Provider to use. | `string` | n/a | yes |
141 | | [container\_definitions](#input\_container\_definitions) | A valid JSON document describing valid container definitions.
See: http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html | `string` | n/a | yes |
142 | | [ecs\_cluster\_id](#input\_ecs\_cluster\_id) | ECS Cluster to deploy to. | `string` | n/a | yes |
143 | | [fqdn](#input\_fqdn) | Fully qualified domain name of load balanced service. | `string` | n/a | yes |
144 | | [load\_balancer\_arn](#input\_load\_balancer\_arn) | ARN of the loadbalancer to associate services with. | `string` | n/a | yes |
145 | | [name](#input\_name) | The name of the resources created by this module.
This value is used as the basis for naming various resources. | `string` | n/a | yes |
146 | | [subnet\_ids](#input\_subnet\_ids) | Subnets associated with the task or service. | `list(string)` | n/a | yes |
147 | | [cpu](#input\_cpu) | The number of cpu units used by the task. If the `requires_compatibilities`
is "FARGATE" this field is required. | `number` | `null` | no |
148 | | [desired\_count](#input\_desired\_count) | The number of service replicas. | `number` | `2` | no |
149 | | [dns\_routing\_weight](#input\_dns\_routing\_weight) | The weight can be a number between 0 and 255. If you specify 0, Route 53
stops responding to DNS queries using this record. | `number` | `255` | no |
150 | | [load\_balancer\_targets](#input\_load\_balancer\_targets) | Load balancer target configs. | `map(any)` | `null` | no |
151 | | [memory](#input\_memory) | The amount (in MiB) of memory used by the task. If the
`requires_compatibilities` is "FARGATE" this field is required. | `number` | `null` | no |
152 | | [placement\_constraints](#input\_placement\_constraints) | Configuration block for rules that are taken into consideration during task
placement. Maximum number of `placement_constraints` is 10. | `map(any)` | `{}` | no |
153 | | [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are EC2 and
FARGATE. | `list(string)` | `null` | no |
154 | | [task\_role\_arn](#input\_task\_role\_arn) | The ARN of IAM role that allows your Amazon ECS container task to make
calls to other AWS services. | `string` | `null` | no |
155 | | [task\_tags](#input\_task\_tags) | Key-value map of resource tags. | `map(string)` | `null` | no |
156 | | [url\_rewrites](#input\_url\_rewrites) | A mapping of fqdns and rewrite rules.
e.x.:
{
foo.dev-empire.com = "https://www.empi.re/listen/index.php?id=$1"
} | `map(any)` | `{}` | no |
157 | | [volumes](#input\_volumes) | List of volume configurations. | `map(any)` | `{}` | no |
158 |
159 | ## Outputs
160 |
161 | | Name | Description |
162 | |------|-------------|
163 | | [result](#output\_result) | The result of the module. |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------