diff --git a/terraform/.header.md b/terraform/.header.md new file mode 100644 index 0000000000..fb57088117 --- /dev/null +++ b/terraform/.header.md @@ -0,0 +1,21 @@ +This module provides a basic Fleet setup. This assumes that you bring nothing to the installation. +If you want to bring your own VPC/database/cache nodes/ECS cluster, then use one of the submodules provided. + +The following is the module layout so you can navigate to the module that you want: + +* Root module (use this to get a Fleet instance ASAP with minimal setup) + * BYO-VPC (use this if you want to install Fleet inside an existing VPC) + * BYO-database (use this if you want to use an existing database and cache node) + * BYO-ECS (use this if you want to bring your own everything but Fleet ECS services) + +# How to improve this module +If this module somehow doesn't fit your needs, feel free to contact us by +opening a ticket, or contacting your contact at Fleet. Our goal is to make this module +fit all needs within AWS, so we will try to find a solution so that this module fits your needs. + +If you want to make the changes yourself, simply make a PR into main with your additions. +We would ask that you make sure that variables are defined as null if there is +no default that makes sense and that variable changes are reflected all the way up the stack. + +# How to update this readme +Edit .header.md and run `terraform-docs markdown . > README.md` diff --git a/terraform/.terraform-docs.yml b/terraform/.terraform-docs.yml new file mode 100644 index 0000000000..1d139ddb40 --- /dev/null +++ b/terraform/.terraform-docs.yml @@ -0,0 +1 @@ +header-from: .header.md diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000000..f31b5a11f8 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,59 @@ +This module provides a basic Fleet setup. This assumes that you bring nothing to the installation. +If you want to bring your own VPC/database/cache nodes/ECS cluster, then use one of the submodules provided. + +The following is the module layout so you can navigate to the module that you want: + +* Root module (use this to get a Fleet instance ASAP with minimal setup) + * BYO-VPC (use this if you want to install Fleet inside an existing VPC) + * BYO-database (use this if you want to use an existing database and cache node) + * BYO-ECS (use this if you want to bring your own everything but Fleet ECS services) + +# How to improve this module +If this module somehow doesn't fit your needs, feel free to contact us by +opening a ticket, or contacting your contact at Fleet. Our goal is to make this module +fit all needs within AWS, so we will try to find a solution so that this module fits your needs. + +If you want to make the changes yourself, simply make a PR into main with your additions. +We would ask that you make sure that variables are defined as null if there is +no default that makes sense and that variable changes are reflected all the way up the stack. + +# How to update this readme +Edit .header.md and run `terraform-docs markdown . > README.md` + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [byo-vpc](#module\_byo-vpc) | ./byo-vpc | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alb\_config](#input\_alb\_config) | n/a |
object({
name = optional(string, "fleet")
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
}) | `{}` | no |
+| [certificate\_arn](#input\_certificate\_arn) | n/a | `string` | n/a | yes |
+| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
}) | {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
} | no |
+| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
} | no |
+| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
}) | {
"cpu": 1024,
"mem": 2048
} | no |
+| [rds\_config](#input\_rds\_config) | The config for the terraform-aws-modules/rds-aurora/aws module | object({
name = optional(string, "fleet")
engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
instance_class = optional(string, "db.t4g.large")
subnets = optional(list(string), [])
allowed_security_groups = optional(list(string), [])
allowed_cidr_blocks = optional(list(string), [])
apply_immediately = optional(bool, true)
monitoring_interval = optional(number, 10)
db_parameter_group_name = optional(string)
db_cluster_parameter_group_name = optional(string)
enabled_cloudwatch_logs_exports = optional(list(string), [])
master_username = optional(string, "fleet")
}) | {
"allowed_cidr_blocks": [],
"allowed_security_groups": [],
"apply_immediately": true,
"db_cluster_parameter_group_name": null,
"db_parameter_group_name": null,
"enabled_cloudwatch_logs_exports": [],
"engine_version": "8.0.mysql_aurora.3.02.2",
"instance_class": "db.t4g.large",
"master_username": "fleet",
"monitoring_interval": 10,
"name": "fleet",
"subnets": []
} | no |
+| [redis\_config](#input\_redis\_config) | n/a | object({
name = optional(string, "fleet")
replication_group_id = optional(string)
elasticache_subnet_group_name = optional(string)
allowed_security_group_ids = optional(list(string), [])
subnets = list(string)
availability_zones = list(string)
cluster_size = optional(number, 3)
instance_type = optional(string, "cache.m5.large")
apply_immediately = optional(bool, true)
automatic_failover_enabled = optional(bool, false)
engine_version = optional(string, "6.x")
family = optional(string, "redis6.x")
at_rest_encryption_enabled = optional(bool, true)
transit_encryption_enabled = optional(bool, true)
parameter = optional(list(object({
name = string
value = string
})), [])
}) | {
"allowed_security_group_ids": [],
"apply_immediately": true,
"at_rest_encryption_enabled": true,
"automatic_failover_enabled": false,
"availability_zones": null,
"cluster_size": 3,
"elasticache_subnet_group_name": null,
"engine_version": "6.x",
"family": "redis6.x",
"instance_type": "cache.m5.large",
"name": "fleet",
"parameter": [],
"replication_group_id": null,
"subnets": null,
"transit_encryption_enabled": true
} | no |
+| [vpc](#input\_vpc) | n/a | object({
name = string
cidr = string
azs = list(string)
private_subnets = list(string)
public_subnets = list(string)
database_subnets = list(string)
elasticache_subnets = list(string)
create_database_subnet_group = bool
create_database_subnet_route_table = bool
create_elasticache_subnet_group = bool
create_elasticache_subnet_route_table = bool
enable_vpn_gateway = bool
one_nat_gateway_per_az = bool
single_nat_gateway = bool
enable_nat_gateway = bool
}) | {
"azs": [
"us-east-2a",
"us-east-2b",
"us-east-2c"
],
"cidr": "10.10.0.0/16",
"create_database_subnet_group": true,
"create_database_subnet_route_table": true,
"create_elasticache_subnet_group": true,
"create_elasticache_subnet_route_table": true,
"database_subnets": [
"10.10.21.0/24",
"10.10.22.0/24",
"10.10.23.0/24"
],
"elasticache_subnets": [
"10.10.31.0/24",
"10.10.32.0/24",
"10.10.33.0/24"
],
"enable_nat_gateway": true,
"enable_vpn_gateway": false,
"name": "fleet",
"one_nat_gateway_per_az": false,
"private_subnets": [
"10.10.1.0/24",
"10.10.2.0/24",
"10.10.3.0/24"
],
"public_subnets": [
"10.10.11.0/24",
"10.10.12.0/24",
"10.10.13.0/24"
],
"single_nat_gateway": true
} | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [byo-vpc](#output\_byo-vpc) | n/a |
diff --git a/terraform/byo-vpc/README.md b/terraform/byo-vpc/README.md
new file mode 100644
index 0000000000..2d6e3962dd
--- /dev/null
+++ b/terraform/byo-vpc/README.md
@@ -0,0 +1,46 @@
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 4.42.0 |
+| [random](#provider\_random) | 3.4.3 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [byo-db](#module\_byo-db) | ./byo-db | n/a |
+| [rds](#module\_rds) | terraform-aws-modules/rds-aurora/aws | 7.6.0 |
+| [redis](#module\_redis) | cloudposse/elasticache-redis/aws | 0.48.0 |
+| [secrets-manager-1](#module\_secrets-manager-1) | lgallard/secrets-manager/aws | 0.6.1 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_db_parameter_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_parameter_group) | resource |
+| [aws_rds_cluster_parameter_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource |
+| [random_password.rds](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
+| [aws_subnet.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
}) | n/a | yes |
+| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
}) | {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
} | no |
+| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
} | no |
+| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
}) | {
"cpu": 1024,
"mem": 2048
} | no |
+| [rds\_config](#input\_rds\_config) | The config for the terraform-aws-modules/rds-aurora/aws module | object({
name = optional(string, "fleet")
engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
instance_class = optional(string, "db.t4g.large")
subnets = optional(list(string), [])
allowed_security_groups = optional(list(string), [])
allowed_cidr_blocks = optional(list(string), [])
apply_immediately = optional(bool, true)
monitoring_interval = optional(number, 10)
db_parameter_group_name = optional(string)
db_cluster_parameter_group_name = optional(string)
enabled_cloudwatch_logs_exports = optional(list(string), [])
master_username = optional(string, "fleet")
}) | {
"allowed_cidr_blocks": [],
"allowed_security_groups": [],
"apply_immediately": true,
"db_cluster_parameter_group_name": null,
"db_parameter_group_name": null,
"enabled_cloudwatch_logs_exports": [],
"engine_version": "8.0.mysql_aurora.3.02.2",
"instance_class": "db.t4g.large",
"master_username": "fleet",
"monitoring_interval": 10,
"name": "fleet",
"subnets": []
} | no |
+| [redis\_config](#input\_redis\_config) | n/a | object({
name = optional(string, "fleet")
replication_group_id = optional(string)
elasticache_subnet_group_name = optional(string)
allowed_security_group_ids = optional(list(string), [])
subnets = list(string)
availability_zones = list(string)
cluster_size = optional(number, 3)
instance_type = optional(string, "cache.m5.large")
apply_immediately = optional(bool, true)
automatic_failover_enabled = optional(bool, false)
engine_version = optional(string, "6.x")
family = optional(string, "redis6.x")
at_rest_encryption_enabled = optional(bool, true)
transit_encryption_enabled = optional(bool, true)
parameter = optional(list(object({
name = string
value = string
})), [])
}) | {
"allowed_security_group_ids": [],
"apply_immediately": true,
"at_rest_encryption_enabled": true,
"automatic_failover_enabled": false,
"availability_zones": null,
"cluster_size": 3,
"elasticache_subnet_group_name": null,
"engine_version": "6.x",
"family": "redis6.x",
"instance_type": "cache.m5.large",
"name": "fleet",
"parameter": [],
"replication_group_id": null,
"subnets": null,
"transit_encryption_enabled": true
} | no |
+| [vpc\_config](#input\_vpc\_config) | n/a | object({
vpc_id = string
networking = object({
subnets = list(string)
})
}) | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [byo-db](#output\_byo-db) | n/a |
diff --git a/terraform/byo-vpc/byo-db/README.md b/terraform/byo-vpc/byo-db/README.md
new file mode 100644
index 0000000000..bbab816c84
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/README.md
@@ -0,0 +1,40 @@
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 4.40.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 8.2.1 |
+| [cluster](#module\_cluster) | terraform-aws-modules/ecs/aws | 4.1.2 |
+| [ecs](#module\_ecs) | ./byo-ecs | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_security_group.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
}) | n/a | yes |
+| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
}) | {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
} | no |
+| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
} | no |
+| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
}) | {
"cpu": 1024,
"mem": 2048
} | no |
+| [vpc\_id](#input\_vpc\_id) | n/a | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [alb](#output\_alb) | n/a |
+| [byo-ecs](#output\_byo-ecs) | n/a |
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/README.md b/terraform/byo-vpc/byo-db/byo-ecs/README.md
new file mode 100644
index 0000000000..b00c75cdb9
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/byo-ecs/README.md
@@ -0,0 +1,51 @@
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 4.39.0 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_appautoscaling_policy.ecs_policy_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource |
+| [aws_appautoscaling_policy.ecs_policy_memory](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource |
+| [aws_appautoscaling_target.ecs_target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource |
+| [aws_cloudwatch_log_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
+| [aws_ecs_service.fleet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
+| [aws_ecs_task_definition.backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
+| [aws_iam_policy.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.role_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_security_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.fleet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.fleet-execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [ecs\_cluster](#input\_ecs\_cluster) | The name of the ECS cluster to use | `string` | n/a | yes |
+| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
} | no |
+| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
}) | {
"cpu": 1024,
"mem": 2048
} | no |
+| [vpc\_id](#input\_vpc\_id) | n/a | `string` | `null` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [security\_groups](#output\_security\_groups) | n/a |
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/iam.tf b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf
new file mode 100644
index 0000000000..b876e38063
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf
@@ -0,0 +1,70 @@
+data "aws_iam_policy_document" "fleet" {
+ statement {
+ effect = "Allow"
+ actions = ["cloudwatch:PutMetricData"]
+ resources = ["*"]
+ }
+
+}
+
+data "aws_iam_policy_document" "assume_role" {
+ statement {
+ effect = "Allow"
+ actions = ["sts:AssumeRole"]
+ principals {
+ identifiers = ["ecs.amazonaws.com", "ecs-tasks.amazonaws.com"]
+ type = "Service"
+ }
+ }
+}
+
+data "aws_iam_policy_document" "fleet-execution" {
+ // allow fleet application to obtain the database password from secrets manager
+ statement {
+ effect = "Allow"
+ actions = ["secretsmanager:GetSecretValue"]
+ resources = [var.fleet_config.database.password_secret_arn]
+ }
+}
+
+resource "aws_iam_role" "main" {
+ count = var.fleet_config.iam_role_arn == null ? 1 : 0
+ name = "fleetdm-role"
+ description = "IAM role that Fleet application assumes when running in ECS"
+ assume_role_policy = data.aws_iam_policy_document.assume_role.json
+}
+
+resource "aws_iam_policy" "main" {
+ count = var.fleet_config.iam_role_arn == null ? 1 : 0
+ name = "fleet-iam-policy"
+ description = "IAM policy that Fleet application uses to define access to AWS resources"
+ policy = data.aws_iam_policy_document.fleet.json
+}
+
+resource "aws_iam_role_policy_attachment" "main" {
+ count = var.fleet_config.iam_role_arn == null ? 1 : 0
+ policy_arn = aws_iam_policy.main[0].arn
+ role = aws_iam_role.main[0].name
+}
+
+resource "aws_iam_policy" "execution" {
+ name = "fleet-iam-policy-execution"
+ description = "IAM policy that Fleet application uses to define access to AWS resources"
+ policy = data.aws_iam_policy_document.fleet-execution.json
+}
+
+resource "aws_iam_role_policy_attachment" "execution" {
+ policy_arn = aws_iam_policy.execution.arn
+ role = aws_iam_role.execution.name
+}
+
+resource "aws_iam_role" "execution" {
+ name = "fleetdm-execution-role"
+ description = "The execution role for Fleet in ECS"
+ assume_role_policy = data.aws_iam_policy_document.assume_role.json
+}
+
+resource "aws_iam_role_policy_attachment" "role_attachment" {
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
+ role = aws_iam_role.execution.name
+}
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/main.tf b/terraform/byo-vpc/byo-db/byo-ecs/main.tf
new file mode 100644
index 0000000000..72eac1e58c
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/byo-ecs/main.tf
@@ -0,0 +1,183 @@
+data "aws_region" "current" {}
+
+resource "aws_ecs_service" "fleet" {
+ name = "fleet"
+ launch_type = "FARGATE"
+ cluster = var.ecs_cluster
+ task_definition = aws_ecs_task_definition.backend.arn
+ desired_count = 1
+ deployment_minimum_healthy_percent = 100
+ deployment_maximum_percent = 200
+ health_check_grace_period_seconds = 30
+
+ load_balancer {
+ target_group_arn = var.fleet_config.loadbalancer.arn
+ container_name = "fleet"
+ container_port = 8080
+ }
+
+ lifecycle {
+ ignore_changes = [desired_count]
+ }
+
+ network_configuration {
+ subnets = var.fleet_config.networking.subnets
+ security_groups = var.fleet_config.networking.security_groups == null ? aws_security_group.main.*.id : var.fleet_config.networking.security_groups
+ }
+}
+
+resource "aws_ecs_task_definition" "backend" {
+ family = "fleet"
+ network_mode = "awsvpc"
+ requires_compatibilities = ["FARGATE"]
+ task_role_arn = var.fleet_config.iam_role_arn == null ? aws_iam_role.main[0].arn : var.fleet_config.iam_role_arn
+ execution_role_arn = aws_iam_role.execution.arn
+ cpu = var.fleet_config.cpu
+ memory = var.fleet_config.mem
+ container_definitions = jsonencode(
+ [
+ {
+ name = "fleet"
+ image = var.fleet_config.image
+ cpu = var.fleet_config.cpu
+ memory = var.fleet_config.mem
+ mountPoints = []
+ volumesFrom = []
+ essential = true
+ portMappings = [
+ {
+ # This port is the same that the contained application also uses
+ containerPort = 8080
+ protocol = "tcp"
+ }
+ ]
+ networkMode = "awsvpc"
+ logConfiguration = {
+ logDriver = "awslogs"
+ options = {
+ awslogs-group = var.fleet_config.awslogs.name == null ? aws_cloudwatch_log_group.main[0].name : var.fleet_config.awslogs.name
+ awslogs-region = var.fleet_config.awslogs.name == null ? data.aws_region.current.name : var.fleet_config.awslogs.region
+ awslogs-stream-prefix = var.fleet_config.awslogs.prefix
+ }
+ },
+ ulimits = [
+ {
+ name = "nofile"
+ softLimit = 999999
+ hardLimit = 999999
+ }
+ ],
+ secrets = [
+ {
+ name = "FLEET_MYSQL_PASSWORD"
+ valueFrom = var.fleet_config.database.password_secret_arn
+ },
+ {
+ name = "FLEET_MYSQL_READ_REPLICA_PASSWORD"
+ valueFrom = var.fleet_config.database.password_secret_arn
+ }
+ ]
+ environment = [
+ {
+ name = "FLEET_MYSQL_USERNAME"
+ value = var.fleet_config.database.user
+ },
+ {
+ name = "FLEET_MYSQL_DATABASE"
+ value = var.fleet_config.database.database
+ },
+ {
+ name = "FLEET_MYSQL_ADDRESS"
+ value = var.fleet_config.database.address
+ },
+ {
+ name = "FLEET_MYSQL_READ_REPLICA_USERNAME"
+ value = var.fleet_config.database.user
+ }, {
+ name = "FLEET_MYSQL_READ_REPLICA_DATABASE"
+ value = var.fleet_config.database.database
+ },
+ {
+ name = "FLEET_MYSQL_READ_REPLICA_ADDRESS"
+ value = var.fleet_config.database.rr_address == null ? var.fleet_config.database.address : var.fleet_config.database.rr_address
+ },
+ {
+ name = "FLEET_REDIS_ADDRESS"
+ value = var.fleet_config.redis.address
+ },
+ {
+ name = "FLEET_REDIS_USE_TLS"
+ value = tostring(var.fleet_config.redis.use_tls)
+ },
+ {
+ name = "FLEET_SERVER_TLS"
+ value = "false"
+ },
+ ]
+ }
+ ])
+}
+
+resource "aws_appautoscaling_target" "ecs_target" {
+ max_capacity = var.fleet_config.autoscaling.max_capacity
+ min_capacity = var.fleet_config.autoscaling.min_capacity
+ resource_id = "service/${var.ecs_cluster}/${aws_ecs_service.fleet.name}"
+ scalable_dimension = "ecs:service:DesiredCount"
+ service_namespace = "ecs"
+}
+
+resource "aws_appautoscaling_policy" "ecs_policy_memory" {
+ name = "fleet-memory-autoscaling"
+ policy_type = "TargetTrackingScaling"
+ resource_id = aws_appautoscaling_target.ecs_target.resource_id
+ scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
+ service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
+
+ target_tracking_scaling_policy_configuration {
+ predefined_metric_specification {
+ predefined_metric_type = "ECSServiceAverageMemoryUtilization"
+ }
+ target_value = var.fleet_config.autoscaling.memory_tracking_target_value
+ }
+}
+
+resource "aws_appautoscaling_policy" "ecs_policy_cpu" {
+ name = "fleet-cpu-autoscaling"
+ policy_type = "TargetTrackingScaling"
+ resource_id = aws_appautoscaling_target.ecs_target.resource_id
+ scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
+ service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
+
+ target_tracking_scaling_policy_configuration {
+ predefined_metric_specification {
+ predefined_metric_type = "ECSServiceAverageCPUUtilization"
+ }
+
+ target_value = var.fleet_config.autoscaling.cpu_tracking_target_value
+ }
+}
+
+resource "aws_cloudwatch_log_group" "main" { #tfsec:ignore:aws-cloudwatch-log-group-customer-key:exp:2022-07-01
+ count = var.fleet_config.awslogs.name == null ? 1 : 0
+ name = "fleetdm"
+ retention_in_days = var.fleet_config.awslogs.retention
+}
+
+resource "aws_security_group" "main" {
+ count = var.fleet_config.security_groups == null ? 1 : 0
+ name = "fleet"
+ vpc_id = var.vpc_id
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ ipv6_cidr_blocks = ["::/0"]
+ }
+ ingress {
+ from_port = 8080
+ to_port = 8080
+ protocol = "TCP"
+ cidr_blocks = ["10.0.0.0/8"]
+ }
+}
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf b/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf
new file mode 100644
index 0000000000..67e62cf55d
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf
@@ -0,0 +1,3 @@
+output "security_groups" {
+ value = var.fleet_config.networking.security_groups == null ? aws_security_group.main.*.id : var.fleet_config.networking.security_groups
+}
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf
new file mode 100644
index 0000000000..18701cbdf9
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf
@@ -0,0 +1,116 @@
+variable "ecs_cluster" {
+ type = string
+ description = "The name of the ECS cluster to use"
+ nullable = false
+}
+
+variable "vpc_id" {
+ type = string
+ default = null
+}
+
+variable "fleet_config" {
+ type = object({
+ mem = optional(number, 512)
+ cpu = optional(number, 256)
+ image = optional(string, "fleetdm/fleet:v4.22.1")
+ extra_environment_variables = optional(map(string), {})
+ extra_secrets = optional(map(string), {})
+ security_groups = optional(list(string), null)
+ iam_role_arn = optional(string, null)
+ database = object({
+ password_secret_arn = string
+ user = string
+ database = string
+ address = string
+ rr_address = optional(string, null)
+ })
+ redis = object({
+ address = string
+ use_tls = optional(bool, true)
+ })
+ awslogs = optional(object({
+ name = optional(string, null)
+ region = optional(string, null)
+ prefix = optional(string, "fleet")
+ retention = optional(number, 5)
+ }), {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ })
+ loadbalancer = object({
+ arn = string
+ })
+ networking = object({
+ subnets = list(string)
+ security_groups = optional(list(string), null)
+ })
+ autoscaling = optional(object({
+ max_capacity = optional(number, 5)
+ min_capacity = optional(number, 1)
+ memory_tracking_target_value = optional(number, 80)
+ cpu_tracking_target_value = optional(number, 80)
+ }), {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ })
+ })
+ default = {
+ mem = 512
+ cpu = 256
+ image = "fleetdm/fleet:v4.22.1"
+ extra_environment_variables = {}
+ extra_secrets = {}
+ security_groups = null
+ iam_role_arn = null
+ database = {
+ password_secret_arn = null
+ user = null
+ database = null
+ address = null
+ rr_address = null
+ }
+ redis = {
+ address = null
+ use_tls = true
+ }
+ awslogs = {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ }
+ loadbalancer = {
+ arn = null
+ }
+ networking = {
+ subnets = null
+ security_groups = null
+ }
+ autoscaling = {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ }
+ }
+ description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified."
+ nullable = false
+}
+
+variable "migration_config" {
+ type = object({
+ mem = number
+ cpu = number
+ })
+ default = {
+ mem = 2048
+ cpu = 1024
+ }
+ description = "The configuration object for Fleet's migration task."
+ nullable = false
+}
diff --git a/terraform/byo-vpc/byo-db/main.tf b/terraform/byo-vpc/byo-db/main.tf
new file mode 100644
index 0000000000..e1470acce6
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/main.tf
@@ -0,0 +1,92 @@
+module "ecs" {
+ source = "./byo-ecs"
+ ecs_cluster = module.cluster.cluster_name
+ fleet_config = merge(var.fleet_config, {
+ loadbalancer = {
+ arn = module.alb.target_group_arns[0]
+ }
+ })
+ migration_config = var.migration_config
+ vpc_id = var.vpc_id
+}
+
+module "cluster" {
+ source = "terraform-aws-modules/ecs/aws"
+ version = "4.1.2"
+
+ autoscaling_capacity_providers = var.ecs_cluster.autoscaling_capacity_providers
+ cluster_configuration = var.ecs_cluster.cluster_configuration
+ cluster_name = var.ecs_cluster.cluster_name
+ cluster_settings = var.ecs_cluster.cluster_settings
+ create = var.ecs_cluster.create
+ default_capacity_provider_use_fargate = var.ecs_cluster.default_capacity_provider_use_fargate
+ fargate_capacity_providers = var.ecs_cluster.fargate_capacity_providers
+ tags = var.ecs_cluster.tags
+}
+
+module "alb" {
+ source = "terraform-aws-modules/alb/aws"
+ version = "8.2.1"
+
+ name = var.alb_config.name
+
+ load_balancer_type = "application"
+
+ vpc_id = var.vpc_id
+ subnets = var.alb_config.subnets
+ security_groups = concat(var.alb_config.security_groups, [aws_security_group.alb.id])
+
+ target_groups = [
+ {
+ name_prefix = var.alb_config.name
+ backend_protocol = "HTTP"
+ backend_port = 80
+ target_type = "ip"
+ health_check = {
+ path = "/healthz"
+ matcher = "200"
+ timeout = 10
+ interval = 15
+ healthy_threshold = 5
+ unhealthy_threshold = 5
+ }
+ }
+ ]
+
+ https_listeners = [
+ {
+ port = 443
+ protocol = "HTTPS"
+ certificate_arn = var.alb_config.certificate_arn
+ target_group_index = 0
+ }
+ ]
+
+ http_tcp_listeners = [
+ {
+ port = 80
+ protocol = "HTTP"
+ target_group_index = 0
+ }
+ ]
+}
+
+resource "aws_security_group" "alb" {
+ vpc_id = var.vpc_id
+ ingress {
+ description = "TLS from VPC"
+ from_port = 443
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ ipv6_cidr_blocks = ["::/0"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ ipv6_cidr_blocks = ["::/0"]
+ }
+}
diff --git a/terraform/byo-vpc/byo-db/outputs.tf b/terraform/byo-vpc/byo-db/outputs.tf
new file mode 100644
index 0000000000..9e94cbb361
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/outputs.tf
@@ -0,0 +1,7 @@
+output "byo-ecs" {
+ value = module.ecs
+}
+
+output "alb" {
+ value = module.alb
+}
diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf
new file mode 100644
index 0000000000..47b90936d1
--- /dev/null
+++ b/terraform/byo-vpc/byo-db/variables.tf
@@ -0,0 +1,165 @@
+variable "vpc_id" {
+ type = string
+}
+
+variable "ecs_cluster" {
+ type = object({
+ autoscaling_capacity_providers = any
+ cluster_configuration = any
+ cluster_name = string
+ cluster_settings = map(string)
+ create = bool
+ default_capacity_provider_use_fargate = bool
+ fargate_capacity_providers = any
+ tags = map(string)
+ })
+ default = {
+ autoscaling_capacity_providers = {}
+ cluster_configuration = {
+ execute_command_configuration = {
+ logging = "OVERRIDE"
+ log_configuration = {
+ cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
+ }
+ }
+ }
+ cluster_name = "fleet"
+ cluster_settings = {
+ "name" : "containerInsights",
+ "value" : "enabled",
+ }
+ create = true
+ default_capacity_provider_use_fargate = true
+ fargate_capacity_providers = {
+ FARGATE = {
+ default_capacity_provider_strategy = {
+ weight = 100
+ }
+ }
+ FARGATE_SPOT = {
+ default_capacity_provider_strategy = {
+ weight = 0
+ }
+ }
+ }
+ tags = {}
+ }
+ description = "The config for the terraform-aws-modules/ecs/aws module"
+ nullable = false
+}
+
+variable "fleet_config" {
+ type = object({
+ mem = optional(number, 512)
+ cpu = optional(number, 256)
+ image = optional(string, "fleetdm/fleet:v4.22.1")
+ extra_environment_variables = optional(map(string), {})
+ extra_secrets = optional(map(string), {})
+ security_groups = optional(list(string), null)
+ iam_role_arn = optional(string, null)
+ database = object({
+ password_secret_arn = string
+ user = string
+ database = string
+ address = string
+ rr_address = optional(string, null)
+ })
+ redis = object({
+ address = string
+ use_tls = optional(bool, true)
+ })
+ awslogs = optional(object({
+ name = optional(string, null)
+ region = optional(string, null)
+ prefix = optional(string, "fleet")
+ retention = optional(number, 5)
+ }), {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ })
+ loadbalancer = object({
+ arn = string
+ })
+ networking = object({
+ subnets = list(string)
+ security_groups = optional(list(string), null)
+ })
+ autoscaling = optional(object({
+ max_capacity = optional(number, 5)
+ min_capacity = optional(number, 1)
+ memory_tracking_target_value = optional(number, 80)
+ cpu_tracking_target_value = optional(number, 80)
+ }), {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ })
+ })
+ default = {
+ mem = 512
+ cpu = 256
+ image = "fleetdm/fleet:v4.22.1"
+ extra_environment_variables = {}
+ extra_secrets = {}
+ security_groups = null
+ iam_role_arn = null
+ database = {
+ password_secret_arn = null
+ user = null
+ database = null
+ address = null
+ rr_address = null
+ }
+ redis = {
+ address = null
+ use_tls = true
+ }
+ awslogs = {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ }
+ loadbalancer = {
+ arn = null
+ }
+ networking = {
+ subnets = null
+ security_groups = null
+ }
+ autoscaling = {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ }
+ }
+ description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified."
+ nullable = false
+}
+
+variable "migration_config" {
+ type = object({
+ mem = number
+ cpu = number
+ })
+ default = {
+ mem = 2048
+ cpu = 1024
+ }
+ description = "The configuration object for Fleet's migration task."
+ nullable = false
+}
+
+variable "alb_config" {
+ type = object({
+ name = optional(string, "fleet")
+ subnets = list(string)
+ security_groups = optional(list(string), [])
+ access_logs = optional(map(string), {})
+ certificate_arn = string
+ })
+}
diff --git a/terraform/byo-vpc/main.tf b/terraform/byo-vpc/main.tf
new file mode 100644
index 0000000000..5e64be58ff
--- /dev/null
+++ b/terraform/byo-vpc/main.tf
@@ -0,0 +1,123 @@
+module "byo-db" {
+ source = "./byo-db"
+ vpc_id = var.vpc_config.vpc_id
+ fleet_config = merge(var.fleet_config, {
+ database = {
+ address = module.rds.cluster_endpoint
+ database = "fleet"
+ user = "fleet"
+ password_secret_arn = module.secrets-manager-1.secret_arns["database-password"]
+ }
+ redis = {
+ address = "${module.redis.endpoint}:${module.redis.port}"
+ }
+ networking = {
+ subnets = var.vpc_config.networking.subnets
+ }
+ })
+ ecs_cluster = var.ecs_cluster
+ migration_config = var.migration_config
+ alb_config = var.alb_config
+}
+
+resource "random_password" "rds" {
+ length = 16
+ special = true
+ override_special = "!#$%&*()-_=+[]{}<>:?"
+}
+
+module "rds" {
+ source = "terraform-aws-modules/rds-aurora/aws"
+ version = "7.6.0"
+
+ name = var.rds_config.name
+ engine = "aurora-mysql"
+ engine_version = var.rds_config.engine_version
+ instance_class = var.rds_config.instance_class
+
+ instances = {
+ one = {}
+ two = {}
+ }
+
+ vpc_id = var.vpc_config.vpc_id
+ subnets = var.rds_config.subnets
+
+ allowed_security_groups = concat(module.byo-db.byo-ecs.security_groups, var.rds_config.allowed_security_groups)
+ allowed_cidr_blocks = var.rds_config.allowed_cidr_blocks
+
+ storage_encrypted = true
+ apply_immediately = var.rds_config.apply_immediately
+ monitoring_interval = var.rds_config.monitoring_interval
+
+ db_parameter_group_name = var.rds_config.db_parameter_group_name == null ? aws_db_parameter_group.main[0].id : var.rds_config.db_parameter_group_name
+ db_cluster_parameter_group_name = var.rds_config.db_cluster_parameter_group_name == null ? aws_rds_cluster_parameter_group.main[0].id : var.rds_config.db_cluster_parameter_group_name
+
+ enabled_cloudwatch_logs_exports = var.rds_config.enabled_cloudwatch_logs_exports
+ master_username = var.rds_config.master_username
+ master_password = random_password.rds.result
+ database_name = "fleet"
+ skip_final_snapshot = true
+}
+
+data "aws_subnet" "redis" {
+ for_each = toset(var.redis_config.subnets)
+ id = each.value
+}
+
+module "redis" {
+ source = "cloudposse/elasticache-redis/aws"
+ version = "0.48.0"
+
+ name = var.redis_config.name
+ replication_group_id = var.redis_config.replication_group_id == null ? var.redis_config.name : var.redis_config.replication_group_id
+ elasticache_subnet_group_name = var.redis_config.elasticache_subnet_group_name == null ? var.redis_config.name : var.redis_config.elasticache_subnet_group_name
+ availability_zones = var.redis_config.availability_zones
+ vpc_id = var.vpc_config.vpc_id
+ description = "lsjdfldjlfjds"
+ #allowed_security_group_ids = concat(var.redis_config.allowed_security_group_ids, module.byo-db.ecs.security_group)
+ subnets = var.redis_config.subnets
+ cluster_size = var.redis_config.cluster_size
+ instance_type = var.redis_config.instance_type
+ apply_immediately = var.redis_config.apply_immediately
+ automatic_failover_enabled = var.redis_config.automatic_failover_enabled
+ engine_version = var.redis_config.engine_version
+ family = var.redis_config.family
+ at_rest_encryption_enabled = var.redis_config.at_rest_encryption_enabled
+ transit_encryption_enabled = var.redis_config.transit_encryption_enabled
+ parameter = var.redis_config.parameter
+ additional_security_group_rules = [{
+ type = "ingress"
+ from_port = 0
+ to_port = 65535
+ protocol = "tcp"
+ cidr_blocks = ["10.0.0.0/8"]
+ }]
+}
+
+module "secrets-manager-1" {
+ source = "lgallard/secrets-manager/aws"
+ version = "0.6.1"
+
+ secrets = {
+ database-password = {
+ description = "fleet-database-password"
+ recovery_window_in_days = 0
+ secret_string = module.rds.cluster_master_password
+ },
+ }
+}
+
+resource "aws_db_parameter_group" "main" {
+ count = var.rds_config.db_parameter_group_name == null ? 1 : 0
+ name = "fleet"
+ family = "aurora-mysql8.0"
+ description = "fleet"
+}
+
+resource "aws_rds_cluster_parameter_group" "main" {
+ count = var.rds_config.db_cluster_parameter_group_name == null ? 1 : 0
+ name = "fleet"
+ family = "aurora-mysql8.0"
+ description = "fleet"
+}
diff --git a/terraform/byo-vpc/outputs.tf b/terraform/byo-vpc/outputs.tf
new file mode 100644
index 0000000000..98ae9b41f7
--- /dev/null
+++ b/terraform/byo-vpc/outputs.tf
@@ -0,0 +1,3 @@
+output "byo-db" {
+ value = module.byo-db
+}
diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf
new file mode 100644
index 0000000000..034133e0cb
--- /dev/null
+++ b/terraform/byo-vpc/variables.tf
@@ -0,0 +1,243 @@
+variable "vpc_config" {
+ type = object({
+ vpc_id = string
+ networking = object({
+ subnets = list(string)
+ })
+ })
+}
+
+variable "rds_config" {
+ type = object({
+ name = optional(string, "fleet")
+ engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
+ instance_class = optional(string, "db.t4g.large")
+ subnets = optional(list(string), [])
+ allowed_security_groups = optional(list(string), [])
+ allowed_cidr_blocks = optional(list(string), [])
+ apply_immediately = optional(bool, true)
+ monitoring_interval = optional(number, 10)
+ db_parameter_group_name = optional(string)
+ db_cluster_parameter_group_name = optional(string)
+ enabled_cloudwatch_logs_exports = optional(list(string), [])
+ master_username = optional(string, "fleet")
+ })
+ default = {
+ name = "fleet"
+ engine_version = "8.0.mysql_aurora.3.02.2"
+ instance_class = "db.t4g.large"
+ subnets = []
+ allowed_security_groups = []
+ allowed_cidr_blocks = []
+ apply_immediately = true
+ monitoring_interval = 10
+ db_parameter_group_name = null
+ db_cluster_parameter_group_name = null
+ enabled_cloudwatch_logs_exports = []
+ master_username = "fleet"
+ }
+ description = "The config for the terraform-aws-modules/rds-aurora/aws module"
+ nullable = false
+}
+
+variable "redis_config" {
+ type = object({
+ name = optional(string, "fleet")
+ replication_group_id = optional(string)
+ elasticache_subnet_group_name = optional(string)
+ allowed_security_group_ids = optional(list(string), [])
+ subnets = list(string)
+ availability_zones = list(string)
+ cluster_size = optional(number, 3)
+ instance_type = optional(string, "cache.m5.large")
+ apply_immediately = optional(bool, true)
+ automatic_failover_enabled = optional(bool, false)
+ engine_version = optional(string, "6.x")
+ family = optional(string, "redis6.x")
+ at_rest_encryption_enabled = optional(bool, true)
+ transit_encryption_enabled = optional(bool, true)
+ parameter = optional(list(object({
+ name = string
+ value = string
+ })), [])
+ })
+ default = {
+ name = "fleet"
+ replication_group_id = null
+ elasticache_subnet_group_name = null
+ allowed_security_group_ids = []
+ subnets = null
+ availability_zones = null
+ cluster_size = 3
+ instance_type = "cache.m5.large"
+ apply_immediately = true
+ automatic_failover_enabled = false
+ engine_version = "6.x"
+ family = "redis6.x"
+ at_rest_encryption_enabled = true
+ transit_encryption_enabled = true
+ parameter = []
+ }
+}
+
+variable "ecs_cluster" {
+ type = object({
+ autoscaling_capacity_providers = any
+ cluster_configuration = any
+ cluster_name = string
+ cluster_settings = map(string)
+ create = bool
+ default_capacity_provider_use_fargate = bool
+ fargate_capacity_providers = any
+ tags = map(string)
+ })
+ default = {
+ autoscaling_capacity_providers = {}
+ cluster_configuration = {
+ execute_command_configuration = {
+ logging = "OVERRIDE"
+ log_configuration = {
+ cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
+ }
+ }
+ }
+ cluster_name = "fleet"
+ cluster_settings = {
+ "name" : "containerInsights",
+ "value" : "enabled",
+ }
+ create = true
+ default_capacity_provider_use_fargate = true
+ fargate_capacity_providers = {
+ FARGATE = {
+ default_capacity_provider_strategy = {
+ weight = 100
+ }
+ }
+ FARGATE_SPOT = {
+ default_capacity_provider_strategy = {
+ weight = 0
+ }
+ }
+ }
+ tags = {}
+ }
+ description = "The config for the terraform-aws-modules/ecs/aws module"
+ nullable = false
+}
+
+variable "fleet_config" {
+ type = object({
+ mem = optional(number, 512)
+ cpu = optional(number, 256)
+ image = optional(string, "fleetdm/fleet:v4.22.1")
+ extra_environment_variables = optional(map(string), {})
+ extra_secrets = optional(map(string), {})
+ security_groups = optional(list(string), null)
+ iam_role_arn = optional(string, null)
+ database = object({
+ password_secret_arn = string
+ user = string
+ database = string
+ address = string
+ rr_address = optional(string, null)
+ })
+ redis = object({
+ address = string
+ use_tls = optional(bool, true)
+ })
+ awslogs = optional(object({
+ name = optional(string, null)
+ region = optional(string, null)
+ prefix = optional(string, "fleet")
+ retention = optional(number, 5)
+ }), {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ })
+ loadbalancer = object({
+ arn = string
+ })
+ networking = object({
+ subnets = list(string)
+ security_groups = optional(list(string), null)
+ })
+ autoscaling = optional(object({
+ max_capacity = optional(number, 5)
+ min_capacity = optional(number, 1)
+ memory_tracking_target_value = optional(number, 80)
+ cpu_tracking_target_value = optional(number, 80)
+ }), {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ })
+ })
+ default = {
+ mem = 512
+ cpu = 256
+ image = "fleetdm/fleet:v4.22.1"
+ extra_environment_variables = {}
+ extra_secrets = {}
+ security_groups = null
+ iam_role_arn = null
+ database = {
+ password_secret_arn = null
+ user = null
+ database = null
+ address = null
+ rr_address = null
+ }
+ redis = {
+ address = null
+ use_tls = true
+ }
+ awslogs = {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ }
+ loadbalancer = {
+ arn = null
+ }
+ networking = {
+ subnets = null
+ security_groups = null
+ }
+ autoscaling = {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ }
+ }
+ description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified."
+ nullable = false
+}
+
+variable "migration_config" {
+ type = object({
+ mem = number
+ cpu = number
+ })
+ default = {
+ mem = 2048
+ cpu = 1024
+ }
+ description = "The configuration object for Fleet's migration task."
+ nullable = false
+}
+
+variable "alb_config" {
+ type = object({
+ name = optional(string, "fleet")
+ subnets = list(string)
+ security_groups = optional(list(string), [])
+ access_logs = optional(map(string), {})
+ certificate_arn = string
+ })
+}
diff --git a/terraform/example/main.tf b/terraform/example/main.tf
new file mode 100644
index 0000000000..ec616a3dd8
--- /dev/null
+++ b/terraform/example/main.tf
@@ -0,0 +1,31 @@
+module "main" {
+ source = "../"
+ certificate_arn = module.acm.acm_certificate_arn
+}
+
+module "acm" {
+ source = "terraform-aws-modules/acm/aws"
+ version = "4.3.1"
+
+ domain_name = "fleet.loadtest.fleetdm.com"
+ zone_id = data.aws_route53_zone.main.id
+
+ wait_for_validation = true
+}
+
+resource "aws_route53_record" "main" {
+ zone_id = data.aws_route53_zone.main.id
+ name = "fleet.loadtest.fleetdm.com"
+ type = "A"
+
+ alias {
+ name = module.main.byo-vpc.byo-db.alb.lb_dns_name
+ zone_id = module.main.byo-vpc.byo-db.alb.lb_zone_id
+ evaluate_target_health = true
+ }
+}
+
+data "aws_route53_zone" "main" {
+ name = "loadtest.fleetdm.com."
+ private_zone = false
+}
diff --git a/terraform/main.tf b/terraform/main.tf
new file mode 100644
index 0000000000..5f44d2b180
--- /dev/null
+++ b/terraform/main.tf
@@ -0,0 +1,41 @@
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+
+ name = var.vpc.name
+ cidr = var.vpc.cidr
+
+ azs = var.vpc.azs
+ private_subnets = var.vpc.private_subnets
+ public_subnets = var.vpc.public_subnets
+ database_subnets = var.vpc.database_subnets
+ elasticache_subnets = var.vpc.elasticache_subnets
+ create_database_subnet_group = var.vpc.create_database_subnet_group
+ create_database_subnet_route_table = var.vpc.create_database_subnet_route_table
+ create_elasticache_subnet_group = var.vpc.create_elasticache_subnet_group
+ create_elasticache_subnet_route_table = var.vpc.create_elasticache_subnet_route_table
+ enable_vpn_gateway = var.vpc.enable_vpn_gateway
+ one_nat_gateway_per_az = var.vpc.one_nat_gateway_per_az
+ single_nat_gateway = var.vpc.single_nat_gateway
+ enable_nat_gateway = var.vpc.enable_nat_gateway
+}
+
+module "byo-vpc" {
+ source = "./byo-vpc"
+ vpc_config = {
+ vpc_id = module.vpc.vpc_id
+ networking = {
+ subnets = module.vpc.private_subnets
+ }
+ }
+ rds_config = merge(var.rds_config, {
+ subnets = module.vpc.database_subnets
+ })
+ redis_config = merge(var.redis_config, {
+ subnets = module.vpc.elasticache_subnets
+ availability_zones = var.vpc.azs
+ })
+ alb_config = merge(var.alb_config, {
+ subnets = module.vpc.public_subnets
+ certificate_arn = var.certificate_arn
+ })
+}
diff --git a/terraform/outputs.tf b/terraform/outputs.tf
new file mode 100644
index 0000000000..c939e4f7ac
--- /dev/null
+++ b/terraform/outputs.tf
@@ -0,0 +1,3 @@
+output "byo-vpc" {
+ value = module.byo-vpc
+}
diff --git a/terraform/variables.tf b/terraform/variables.tf
new file mode 100644
index 0000000000..82105860e4
--- /dev/null
+++ b/terraform/variables.tf
@@ -0,0 +1,276 @@
+variable "vpc" {
+ type = object({
+ name = string
+ cidr = string
+ azs = list(string)
+ private_subnets = list(string)
+ public_subnets = list(string)
+ database_subnets = list(string)
+ elasticache_subnets = list(string)
+
+ create_database_subnet_group = bool
+ create_database_subnet_route_table = bool
+ create_elasticache_subnet_group = bool
+ create_elasticache_subnet_route_table = bool
+ enable_vpn_gateway = bool
+ one_nat_gateway_per_az = bool
+ single_nat_gateway = bool
+ enable_nat_gateway = bool
+ })
+ default = {
+ name = "fleet"
+ cidr = "10.10.0.0/16"
+ azs = ["us-east-2a", "us-east-2b", "us-east-2c"]
+ private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"]
+ public_subnets = ["10.10.11.0/24", "10.10.12.0/24", "10.10.13.0/24"]
+ database_subnets = ["10.10.21.0/24", "10.10.22.0/24", "10.10.23.0/24"]
+ elasticache_subnets = ["10.10.31.0/24", "10.10.32.0/24", "10.10.33.0/24"]
+
+ create_database_subnet_group = true
+ create_database_subnet_route_table = true
+ create_elasticache_subnet_group = true
+ create_elasticache_subnet_route_table = true
+ enable_vpn_gateway = false
+ one_nat_gateway_per_az = false
+ single_nat_gateway = true
+ enable_nat_gateway = true
+ }
+}
+
+variable "certificate_arn" {
+ type = string
+}
+
+variable "rds_config" {
+ type = object({
+ name = optional(string, "fleet")
+ engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
+ instance_class = optional(string, "db.t4g.large")
+ subnets = optional(list(string), [])
+ allowed_security_groups = optional(list(string), [])
+ allowed_cidr_blocks = optional(list(string), [])
+ apply_immediately = optional(bool, true)
+ monitoring_interval = optional(number, 10)
+ db_parameter_group_name = optional(string)
+ db_cluster_parameter_group_name = optional(string)
+ enabled_cloudwatch_logs_exports = optional(list(string), [])
+ master_username = optional(string, "fleet")
+ })
+ default = {
+ name = "fleet"
+ engine_version = "8.0.mysql_aurora.3.02.2"
+ instance_class = "db.t4g.large"
+ subnets = []
+ allowed_security_groups = []
+ allowed_cidr_blocks = []
+ apply_immediately = true
+ monitoring_interval = 10
+ db_parameter_group_name = null
+ db_cluster_parameter_group_name = null
+ enabled_cloudwatch_logs_exports = []
+ master_username = "fleet"
+ }
+ description = "The config for the terraform-aws-modules/rds-aurora/aws module"
+ nullable = false
+}
+
+variable "redis_config" {
+ type = object({
+ name = optional(string, "fleet")
+ replication_group_id = optional(string)
+ elasticache_subnet_group_name = optional(string)
+ allowed_security_group_ids = optional(list(string), [])
+ subnets = list(string)
+ availability_zones = list(string)
+ cluster_size = optional(number, 3)
+ instance_type = optional(string, "cache.m5.large")
+ apply_immediately = optional(bool, true)
+ automatic_failover_enabled = optional(bool, false)
+ engine_version = optional(string, "6.x")
+ family = optional(string, "redis6.x")
+ at_rest_encryption_enabled = optional(bool, true)
+ transit_encryption_enabled = optional(bool, true)
+ parameter = optional(list(object({
+ name = string
+ value = string
+ })), [])
+ })
+ default = {
+ name = "fleet"
+ replication_group_id = null
+ elasticache_subnet_group_name = null
+ allowed_security_group_ids = []
+ subnets = null
+ availability_zones = null
+ cluster_size = 3
+ instance_type = "cache.m5.large"
+ apply_immediately = true
+ automatic_failover_enabled = false
+ engine_version = "6.x"
+ family = "redis6.x"
+ at_rest_encryption_enabled = true
+ transit_encryption_enabled = true
+ parameter = []
+ }
+}
+
+variable "ecs_cluster" {
+ type = object({
+ autoscaling_capacity_providers = any
+ cluster_configuration = any
+ cluster_name = string
+ cluster_settings = map(string)
+ create = bool
+ default_capacity_provider_use_fargate = bool
+ fargate_capacity_providers = any
+ tags = map(string)
+ })
+ default = {
+ autoscaling_capacity_providers = {}
+ cluster_configuration = {
+ execute_command_configuration = {
+ logging = "OVERRIDE"
+ log_configuration = {
+ cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
+ }
+ }
+ }
+ cluster_name = "fleet"
+ cluster_settings = {
+ "name" : "containerInsights",
+ "value" : "enabled",
+ }
+ create = true
+ default_capacity_provider_use_fargate = true
+ fargate_capacity_providers = {
+ FARGATE = {
+ default_capacity_provider_strategy = {
+ weight = 100
+ }
+ }
+ FARGATE_SPOT = {
+ default_capacity_provider_strategy = {
+ weight = 0
+ }
+ }
+ }
+ tags = {}
+ }
+ description = "The config for the terraform-aws-modules/ecs/aws module"
+ nullable = false
+}
+
+variable "fleet_config" {
+ type = object({
+ mem = optional(number, 512)
+ cpu = optional(number, 256)
+ image = optional(string, "fleetdm/fleet:v4.22.1")
+ extra_environment_variables = optional(map(string), {})
+ extra_secrets = optional(map(string), {})
+ security_groups = optional(list(string), null)
+ iam_role_arn = optional(string, null)
+ database = object({
+ password_secret_arn = string
+ user = string
+ database = string
+ address = string
+ rr_address = optional(string, null)
+ })
+ redis = object({
+ address = string
+ use_tls = optional(bool, true)
+ })
+ awslogs = optional(object({
+ name = optional(string, null)
+ region = optional(string, null)
+ prefix = optional(string, "fleet")
+ retention = optional(number, 5)
+ }), {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ })
+ loadbalancer = object({
+ arn = string
+ })
+ networking = object({
+ subnets = list(string)
+ security_groups = optional(list(string), null)
+ })
+ autoscaling = optional(object({
+ max_capacity = optional(number, 5)
+ min_capacity = optional(number, 1)
+ memory_tracking_target_value = optional(number, 80)
+ cpu_tracking_target_value = optional(number, 80)
+ }), {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ })
+ })
+ default = {
+ mem = 512
+ cpu = 256
+ image = "fleetdm/fleet:v4.22.1"
+ extra_environment_variables = {}
+ extra_secrets = {}
+ security_groups = null
+ iam_role_arn = null
+ database = {
+ password_secret_arn = null
+ user = null
+ database = null
+ address = null
+ rr_address = null
+ }
+ redis = {
+ address = null
+ use_tls = true
+ }
+ awslogs = {
+ name = null
+ region = null
+ prefix = "fleet"
+ retention = 5
+ }
+ loadbalancer = {
+ arn = null
+ }
+ networking = {
+ subnets = null
+ security_groups = null
+ }
+ autoscaling = {
+ max_capacity = 5
+ min_capacity = 1
+ memory_tracking_target_value = 80
+ cpu_tracking_target_value = 80
+ }
+ }
+ description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified."
+ nullable = false
+}
+
+variable "migration_config" {
+ type = object({
+ mem = number
+ cpu = number
+ })
+ description = "The configuration object for Fleet's migration task."
+ nullable = false
+ default = {
+ mem = 2048
+ cpu = 1024
+ }
+}
+
+variable "alb_config" {
+ type = object({
+ name = optional(string, "fleet")
+ security_groups = optional(list(string), [])
+ access_logs = optional(map(string), {})
+ })
+ default = {}
+}