From 3f98b74197d433d857dceed629d6c26f7680cf7d Mon Sep 17 00:00:00 2001 From: Frederic Leger Date: Thu, 16 May 2024 15:34:51 +0200 Subject: [PATCH] feat(aws): add rds-mysql --- aws/rds-mysql-cluster/.terraform.lock.hcl | 26 +++ aws/rds-mysql-cluster/.tflint.hcl | 10 + aws/rds-mysql-cluster/README.md | 85 ++++++++ aws/rds-mysql-cluster/main.tf | 17 ++ aws/rds-mysql-cluster/outputs.tf | 15 ++ aws/rds-mysql-cluster/providers.tf | 17 ++ aws/rds-mysql-cluster/rds.tf | 114 +++++++++++ aws/rds-mysql-cluster/variables.tf | 227 ++++++++++++++++++++++ 8 files changed, 511 insertions(+) create mode 100644 aws/rds-mysql-cluster/.terraform.lock.hcl create mode 100644 aws/rds-mysql-cluster/.tflint.hcl create mode 100644 aws/rds-mysql-cluster/README.md create mode 100644 aws/rds-mysql-cluster/main.tf create mode 100644 aws/rds-mysql-cluster/outputs.tf create mode 100644 aws/rds-mysql-cluster/providers.tf create mode 100644 aws/rds-mysql-cluster/rds.tf create mode 100644 aws/rds-mysql-cluster/variables.tf diff --git a/aws/rds-mysql-cluster/.terraform.lock.hcl b/aws/rds-mysql-cluster/.terraform.lock.hcl new file mode 100644 index 0000000..ba74b4f --- /dev/null +++ b/aws/rds-mysql-cluster/.terraform.lock.hcl @@ -0,0 +1,26 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.67.0" + constraints = ">= 4.30.0, < 5.0.0" + hashes = [ + "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.9.2" + constraints = "~> 0.9.1" + hashes = [ + "h1:M93amXwO9KelOaPiyXGak1aiIyf6pYo+FDr6pigIb6M=", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.5" + constraints = "~> 4.0.3" + hashes = [ + "h1:zeG5RmggBZW/8JWIVrdaeSJa0OG62uFX5HY1eE8SjzY=", + ] +} diff --git a/aws/rds-mysql-cluster/.tflint.hcl b/aws/rds-mysql-cluster/.tflint.hcl new file mode 100644 index 0000000..e2fc6bf --- /dev/null +++ b/aws/rds-mysql-cluster/.tflint.hcl @@ -0,0 +1,10 @@ +plugin "terraform" { + enabled = true + preset = "recommended" +} + +plugin "aws" { + enabled = true + version = "0.30.0" + source = "github.com/terraform-linters/tflint-ruleset-aws" +} diff --git a/aws/rds-mysql-cluster/README.md b/aws/rds-mysql-cluster/README.md new file mode 100644 index 0000000..c8af95e --- /dev/null +++ b/aws/rds-mysql-cluster/README.md @@ -0,0 +1,85 @@ +# RDS mysql cluster + +Create an RDS cluster in AWS with any number of instances. + +- Supports only Aurora MySQL +- Supports serverless clusters + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.3 | +| [aws](#requirement\_aws) | >= 4.30.0, < 5.0.0 | +| [time](#requirement\_time) | ~> 0.9.1 | +| [tls](#requirement\_tls) | ~> 4.0.3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.30.0, < 5.0.0 | +| [time](#provider\_time) | ~> 0.9.1 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_subnet_group.subnet_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource | +| [aws_rds_cluster.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster) | resource | +| [aws_rds_cluster_instance.instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_instance) | resource | +| [aws_rds_cluster_parameter_group.params](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource | +| [time_static.last_update](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/static) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [apply\_immediately](#input\_apply\_immediately) | Specifies whether any database modifications are applied immediately, or during the next maintenance window | `bool` | `true` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window | `bool` | `true` | no | +| [backup\_retention\_period](#input\_backup\_retention\_period) | The days to retain backups for | `number` | `7` | no | +| [copy\_tags\_to\_snapshot](#input\_copy\_tags\_to\_snapshot) | On delete, copy all Instance tags to the final snapshot | `bool` | `true` | no | +| [customer](#input\_customer) | Customer for the current deployment | `string` | `""` | no | +| [database\_name](#input\_database\_name) | The name of the database to create when the DB instance is created | `string` | `null` | no | +| [db\_parameter\_group\_family](#input\_db\_parameter\_group\_family) | The family of the DB parameter group | `string` | `"aurora-mysql5.7"` | no | +| [db\_parameter\_group\_parameters](#input\_db\_parameter\_group\_parameters) | A list of DB parameters to apply |
list(object({
name = string
value = string
}))
| `null` | no | +| [default\_instance\_class](#input\_default\_instance\_class) | The default instance class to use for instances | `string` | `"db.t3.small"` | no | +| [deletion\_protection](#input\_deletion\_protection) | If the DB instance should have deletion protection enabled | `bool` | `false` | no | +| [enabled\_cloudwatch\_logs\_exports](#input\_enabled\_cloudwatch\_logs\_exports) | A list of log types that need to be enabled for exporting to CloudWatch Logs. Note that this is not supported on serverless engine. | `list(string)` | `null` | no | +| [engine](#input\_engine) | The database engine to use | `string` | `"aurora-mysql"` | no | +| [engine\_mode](#input\_engine\_mode) | The database engine mode to use | `string` | `"serverless"` | no | +| [engine\_version](#input\_engine\_version) | The engine version to use | `string` | `"8.0.mysql_aurora.3.02.2"` | no | +| [environment](#input\_environment) | Environment for the current deployment | `string` | `""` | no | +| [iam\_database\_authentication\_enabled](#input\_iam\_database\_authentication\_enabled) | Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled | `bool` | `false` | no | +| [iam\_roles](#input\_iam\_roles) | A list of ARNs for the IAM roles to associate with the DB instance | `list(string)` | `null` | no | +| [instances](#input\_instances) | A list of DB instances to create |
list(object({
name = string
instance_class = string
publicly_accessible = bool
availability_zone = string
}))
| `null` | no | +| [name](#input\_name) | The name of the launch template | `string` | n/a | yes | +| [password](#input\_password) | Password for the master DB user | `string` | `"root"` | no | +| [performance\_insights\_enabled](#input\_performance\_insights\_enabled) | Specifies whether Performance Insights is enabled | `bool` | `true` | no | +| [port](#input\_port) | The port on which the DB accepts connections | `number` | `3306` | no | +| [preferred\_backup\_window](#input\_preferred\_backup\_window) | The daily time range (in UTC) during which automated backups are created if they are enabled | `string` | `"02:00-03:00"` | no | +| [preferred\_maintenance\_window](#input\_preferred\_maintenance\_window) | The weekly time range (in UTC) during which system maintenance can occur | `string` | `"sun:03:00-sun:04:00"` | no | +| [rds\_enhanced\_monitoring\_interval](#input\_rds\_enhanced\_monitoring\_interval) | The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance | `number` | `0` | no | +| [rds\_enhanced\_monitoring\_role\_arn](#input\_rds\_enhanced\_monitoring\_role\_arn) | The ARN of the IAM role that allows Amazon RDS to send enhanced monitoring metrics to CloudWatch Logs | `string` | `null` | no | +| [scaling\_configuration](#input\_scaling\_configuration) | A scaling configuration block | `any` | `null` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | A list of VPC security groups to associate | `list(string)` | `null` | no | +| [serverlessv2\_scaling\_configuration](#input\_serverlessv2\_scaling\_configuration) | remember it's only valid when engine mode is set to "provisioned" | `any` | `null` | no | +| [storage\_encrypted](#input\_storage\_encrypted) | Specifies whether the DB instance is encrypted | `bool` | `true` | no | +| [subnet\_ids](#input\_subnet\_ids) | A list of VPC subnet IDs | `list(string)` | `null` | no | +| [tags](#input\_tags) | Default tags to add to resources | `map(any)` | `{}` | no | +| [username](#input\_username) | Username for the master DB user | `string` | `"root"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | n/a | +| [endpoint](#output\_endpoint) | n/a | +| [id](#output\_id) | n/a | +| [reader\_endpoint](#output\_reader\_endpoint) | n/a | + diff --git a/aws/rds-mysql-cluster/main.tf b/aws/rds-mysql-cluster/main.tf new file mode 100644 index 0000000..791fc76 --- /dev/null +++ b/aws/rds-mysql-cluster/main.tf @@ -0,0 +1,17 @@ +locals { + # tflint-ignore: terraform_unused_declarations + interpolated_tags = merge({ + "Name" = var.name, + "Customer" = var.customer, + "Environment" = var.environment, + "ManagedBy" = "Terraform", + "LastModifiedAt" = time_static.last_update.rfc3339, + }, + var.tags + ) + instances_lenght = var.instances == null ? 0 : length(var.instances) + instances_count = var.engine_mode == "serverless" ? 0 : local.instances_lenght +} + +resource "time_static" "last_update" { +} diff --git a/aws/rds-mysql-cluster/outputs.tf b/aws/rds-mysql-cluster/outputs.tf new file mode 100644 index 0000000..8e71ef8 --- /dev/null +++ b/aws/rds-mysql-cluster/outputs.tf @@ -0,0 +1,15 @@ +output "id" { + value = aws_rds_cluster.cluster.id +} + +output "arn" { + value = aws_rds_cluster.cluster.arn +} + +output "endpoint" { + value = aws_rds_cluster.cluster.endpoint +} + +output "reader_endpoint" { + value = aws_rds_cluster.cluster.reader_endpoint +} diff --git a/aws/rds-mysql-cluster/providers.tf b/aws/rds-mysql-cluster/providers.tf new file mode 100644 index 0000000..3c4cf81 --- /dev/null +++ b/aws/rds-mysql-cluster/providers.tf @@ -0,0 +1,17 @@ +terraform { + required_version = "~> 1.3" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.30.0, < 5.0.0" + } + time = { + source = "hashicorp/time" + version = "~> 0.9.1" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0.3" + } + } +} diff --git a/aws/rds-mysql-cluster/rds.tf b/aws/rds-mysql-cluster/rds.tf new file mode 100644 index 0000000..8156d47 --- /dev/null +++ b/aws/rds-mysql-cluster/rds.tf @@ -0,0 +1,114 @@ +resource "aws_db_subnet_group" "subnet_group" { + name = var.name + subnet_ids = var.subnet_ids + tags = local.interpolated_tags +} + +resource "aws_rds_cluster_parameter_group" "params" { + name = var.name + family = var.db_parameter_group_family + description = "${var.name} parameters group" + dynamic "parameter" { + for_each = var.db_parameter_group_parameters == null ? [] : var.db_parameter_group_parameters + + content { + name = parameter.value.name + value = parameter.value.value + } + } + + tags = local.interpolated_tags +} + +#trivy:ignore:aws-rds-encrypt-cluster-storage-data +#trivy:ignore:AVD-AWS-0343 protection is activated by default +resource "aws_rds_cluster" "cluster" { + #checkov:skip=CKV2_AWS_8:it's not module responsibility to create a backup plan + #checkov:skip=CKV_AWS_20:RDS cluster is encrypted + #checkov:skip=CKV_AWS_118:we don't want enhanced monitoring for now + #checkov:skip=CKV_AWS_128:we don't want to deal with IAM authentication + #checkov:skip=CKV_AWS_139:deletion protection is activated by default + #checkov:skip=CKV_AWS_162:we don't want to deal with IAM authentication + #checkov:skip=CKV_AWS_324:we don't want log capture + #checkov:skip=CKV_AWS_327:we don't want using kms key + #checkov:skip=CKV_AWS_354:we don't need performance insights + + cluster_identifier = var.name + engine = var.engine + engine_mode = var.engine_mode + engine_version = var.engine_version + database_name = var.database_name + master_username = var.username + master_password = var.password + final_snapshot_identifier = "${var.name}-final-snapshot" + skip_final_snapshot = false + deletion_protection = var.deletion_protection + backup_retention_period = var.backup_retention_period + preferred_backup_window = var.preferred_backup_window + preferred_maintenance_window = var.preferred_maintenance_window + port = var.port + db_subnet_group_name = aws_db_subnet_group.subnet_group.name + vpc_security_group_ids = var.security_group_ids + storage_encrypted = var.storage_encrypted + apply_immediately = var.apply_immediately + db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.params.name + iam_database_authentication_enabled = var.iam_database_authentication_enabled + copy_tags_to_snapshot = var.copy_tags_to_snapshot + iam_roles = var.iam_roles + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports + + dynamic "scaling_configuration" { + for_each = var.scaling_configuration != null ? [var.scaling_configuration] : [] + + content { + auto_pause = lookup(scaling_configuration.value, "auto_pause", null) + max_capacity = lookup(scaling_configuration.value, "max_capacity", null) + min_capacity = lookup(scaling_configuration.value, "min_capacity", null) + seconds_until_auto_pause = lookup(scaling_configuration.value, "seconds_until_auto_pause", null) + timeout_action = lookup(scaling_configuration.value, "timeout_action", null) + } + } + + dynamic "serverlessv2_scaling_configuration" { + for_each = var.serverlessv2_scaling_configuration != null ? [var.serverlessv2_scaling_configuration] : [] + + content { + min_capacity = lookup(serverlessv2_scaling_configuration.value, "min_capacity", null) + max_capacity = lookup(serverlessv2_scaling_configuration.value, "max_capacity", null) + } + } + + tags = local.interpolated_tags +} + +resource "aws_rds_cluster_instance" "instance" { + #checkov:skip=CKV_AWS_118:we don't want enhanced monitoring for now + #checkov:skip=CKV_AWS_273:we don't use IAM users or SSO + #checkov:skip=CKV_AWS_354:we don't need performance insights + + count = local.instances_count + identifier = var.instances[count.index].name + cluster_identifier = aws_rds_cluster.cluster.id + engine = aws_rds_cluster.cluster.engine + engine_version = aws_rds_cluster.cluster.engine_version + instance_class = lookup(var.instances[count.index], "instance_class", var.default_instance_class) + publicly_accessible = lookup(var.instances[count.index], "publicly_accessible", false) + availability_zone = lookup(var.instances[count.index], "availability_zone", null) + db_subnet_group_name = aws_db_subnet_group.subnet_group.name + apply_immediately = var.apply_immediately + monitoring_role_arn = var.rds_enhanced_monitoring_role_arn + monitoring_interval = var.rds_enhanced_monitoring_interval + auto_minor_version_upgrade = var.auto_minor_version_upgrade + performance_insights_enabled = var.performance_insights_enabled + copy_tags_to_snapshot = var.copy_tags_to_snapshot + + # Updating engine version forces replacement of instances, and they shouldn't be replaced + # because cluster will update them if engine version is changed + lifecycle { + ignore_changes = [ + engine_version + ] + } + + tags = local.interpolated_tags +} diff --git a/aws/rds-mysql-cluster/variables.tf b/aws/rds-mysql-cluster/variables.tf new file mode 100644 index 0000000..803bee5 --- /dev/null +++ b/aws/rds-mysql-cluster/variables.tf @@ -0,0 +1,227 @@ +variable "name" { + description = "The name of the launch template" + type = string +} + +variable "customer" { + description = "Customer for the current deployment" + type = string + default = "" +} + +variable "environment" { + description = "Environment for the current deployment" + type = string + default = "" +} + +variable "tags" { + description = "Default tags to add to resources" + type = map(any) + default = {} +} + +# module specific variables +variable "engine" { + description = "The database engine to use" + type = string + default = "aurora-mysql" +} + +variable "engine_mode" { + description = "The database engine mode to use" + type = string + default = "serverless" +} + +variable "engine_version" { + description = "The engine version to use" + type = string + default = "8.0.mysql_aurora.3.02.2" +} + +variable "database_name" { + description = "The name of the database to create when the DB instance is created" + type = string + default = null +} + +variable "username" { + description = "Username for the master DB user" + type = string + default = "root" +} + +variable "password" { + description = "Password for the master DB user" + type = string + default = "root" +} + +variable "deletion_protection" { + description = "If the DB instance should have deletion protection enabled" + type = bool + default = false +} + +variable "backup_retention_period" { + description = "The days to retain backups for" + type = number + default = 7 +} + +variable "preferred_backup_window" { + description = "The daily time range (in UTC) during which automated backups are created if they are enabled" + type = string + default = "02:00-03:00" +} + +variable "preferred_maintenance_window" { + description = "The weekly time range (in UTC) during which system maintenance can occur" + type = string + default = "sun:03:00-sun:04:00" +} + +variable "port" { + description = "The port on which the DB accepts connections" + type = number + default = 3306 +} + +variable "security_group_ids" { + description = "A list of VPC security groups to associate" + type = list(string) + default = null +} + +variable "storage_encrypted" { + description = "Specifies whether the DB instance is encrypted" + type = bool + default = true +} + +variable "apply_immediately" { + description = "Specifies whether any database modifications are applied immediately, or during the next maintenance window" + type = bool + default = true +} + +variable "subnet_ids" { + description = "A list of VPC subnet IDs" + type = list(string) + default = null +} + +variable "iam_database_authentication_enabled" { + description = "Specifies whether or mappings of AWS Identity and Access Management (IAM) accounts to database accounts is enabled" + type = bool + default = false +} + +variable "copy_tags_to_snapshot" { + description = "On delete, copy all Instance tags to the final snapshot" + type = bool + default = true +} + +variable "iam_roles" { + description = "A list of ARNs for the IAM roles to associate with the DB instance" + type = list(string) + default = null +} + +variable "enabled_cloudwatch_logs_exports" { + description = "A list of log types that need to be enabled for exporting to CloudWatch Logs. Note that this is not supported on serverless engine." + type = list(string) + default = null +} + +variable "scaling_configuration" { + description = "A scaling configuration block" + type = any + default = null + validation { + condition = var.scaling_configuration != null ? alltrue([ + contains(keys(var.scaling_configuration), "auto_pause"), + contains(keys(var.scaling_configuration), "max_capacity"), + contains(keys(var.scaling_configuration), "min_capacity"), + contains(keys(var.scaling_configuration), "seconds_until_auto_pause"), + contains(keys(var.scaling_configuration), "timeout_action"), + can(tobool(var.scaling_configuration.auto_pause)), + can(tonumber(var.scaling_configuration.max_capacity)), + can(tonumber(var.scaling_configuration.min_capacity)), + can(tonumber(var.scaling_configuration.seconds_until_auto_pause)), + can(tostring(var.scaling_configuration.timeout_action)) + ]) : true + error_message = "scaling_configuration must be a map with the keys: auto_pause, max_capacity, min_capacity, seconds_until_auto_pause, and timeout_action." + } +} + +# remember it's only valid when engine mode is set to "provisioned" +variable "serverlessv2_scaling_configuration" { + type = any + default = null + validation { + condition = var.serverlessv2_scaling_configuration == null || ( + contains(keys(var.serverlessv2_scaling_configuration), "min_capacity") && + contains(keys(var.serverlessv2_scaling_configuration), "max_capacity") && + var.serverlessv2_scaling_configuration.min_capacity <= var.serverlessv2_scaling_configuration.max_capacity + ) + error_message = "The serverlessv2_scaling_configuration must be a map with the keys: min_capacity and max_capacity, where min_capacity is less than or equal to max_capacity." + } +} +variable "db_parameter_group_family" { + description = "The family of the DB parameter group" + type = string + default = "aurora-mysql5.7" +} + +variable "db_parameter_group_parameters" { + description = "A list of DB parameters to apply" + default = null + type = list(object({ + name = string + value = string + })) +} + +variable "instances" { + description = "A list of DB instances to create" + default = null + type = list(object({ + name = string + instance_class = string + publicly_accessible = bool + availability_zone = string + })) +} + +variable "default_instance_class" { + description = "The default instance class to use for instances" + type = string + default = "db.t3.small" +} + +variable "rds_enhanced_monitoring_role_arn" { + description = "The ARN of the IAM role that allows Amazon RDS to send enhanced monitoring metrics to CloudWatch Logs" + type = string + default = null +} + +variable "rds_enhanced_monitoring_interval" { + description = "The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance" + type = number + default = 0 +} + +variable "auto_minor_version_upgrade" { + description = "Indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window" + type = bool + default = true +} + +variable "performance_insights_enabled" { + description = "Specifies whether Performance Insights is enabled" + type = bool + default = true +}