diff --git a/README.md b/README.md index 4a21007..e6801e7 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,28 @@ -# please enable git hooks by running the following command -```sh -git config --global core.hooksPath ./githooks # enables git hooks globally -``` -# How to use +## This terraform module allows to create aws rds cluster by using various engine types and configurations, it allows also to enable/create rds cluster attached rds proxy -Case 1. Create Security group and create RDS +## module upgrade guide +- from <1.4.0 versions to >=1.4.0 version upgrade + - make sure you moved the state of "db" underlying module by using command like following + ```sh + terraform state mv module..module.db module..module.db[0] + ``` + - if you had no storage_type set explicitly then set it to "gp2" -``` + + +## How to use (more examples/tests can be found in [./tests](./tests) folder) + +### Case 1. Create Security group and create RDS + +```terraform data "aws_vpc" "main" { - id = "vpc-04c3b2abe39cd8a6a" + id = "vpc-xxxxxxx" } module "rds" { - source = "dasmeta/modules/aws//modules/rds" + source = "dasmeta/rds/aws" + version = "1.4.0" + allocated_storage = 20 storage_type = "gp2" engine = "mysql" @@ -24,33 +34,17 @@ module "rds" { db_password = "some-password" parameter_group_name = "default.mysql5.7" vpc_id = "${data.aws_vpc.main.id}" - subnet_ids = ["subnet-04ad8ad2fdec889ec","subnet-0ea0a01c1bea0a0c9"] - - create_security_group = true - ingress_with_cidr_blocks = [ - { - description = "3306 from VPC" - from_port = 3306 - to_port = 3306 - protocol = "tcp" - cidr_blocks = "${data.aws_vpc.main.cidr_block}" - }] - - egress_with_cidr_blocks = [ - { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks ="[0.0.0.0/0]" - }] + subnet_ids = ["subnet-xxxxxxxx","subnet-xxxxxx"] } ``` -Case 2. Create RDS +### Case 2. Create RDS and pass custom/external created security group ids -``` +```terraform module "rds" { - source = "dasmeta/modules/aws//modules/rds" + source = "dasmeta/rds/aws" + version = "1.4.0" + allocated_storage = 20 storage_type = "gp2" engine = "mysql" @@ -62,14 +56,20 @@ module "rds" { db_password = "some-password" parameter_group_name = "default.mysql5.7" - vpc_id = "vpc-04c3b2abe39cd8a6a" - subnet_ids = ["subnet-04ad8ad2fdec889ec","subnet-0ea0a01c1bea0a0c9"] + vpc_id = "vpc-xxxxxxxxxxxx" + subnet_ids = ["subnet-xxxxxxx","subnet-xxxxxxxx"] create_security_group = false -// vpc_security_group_ids = ["sg-062742ac7a7f8c7a7"] + vpc_security_group_ids = ["sg-xxxxxxxxx"] } ``` +## contribution +### please enable git hooks by running the following command +```sh +git config --global core.hooksPath ./githooks # enables git hooks globally +``` + ## Requirements @@ -87,8 +87,10 @@ No requirements. |------|--------|---------| | [cloudwatch\_metric\_filters](#module\_cloudwatch\_metric\_filters) | dasmeta/monitoring/aws//modules/cloudwatch-log-based-metrics | 1.13.2 | | [cw\_alerts](#module\_cw\_alerts) | dasmeta/monitoring/aws//modules/alerts | 1.3.5 | -| [db](#module\_db) | terraform-aws-modules/rds/aws | ~> 6.1 | -| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | 4.7.0 | +| [db](#module\_db) | terraform-aws-modules/rds/aws | 6.10.0 | +| [db\_aurora](#module\_db\_aurora) | terraform-aws-modules/rds-aurora/aws | 9.11.0 | +| [proxy](#module\_proxy) | ./modules/proxy | n/a | +| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | 5.2.0 | ## Resources @@ -96,6 +98,7 @@ No requirements. |------|------| | [aws_db_instance.database](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/db_instance) | data source | | [aws_ec2_instance_type.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_instance_type) | data source | +| [aws_vpc.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | ## Inputs @@ -104,6 +107,7 @@ No requirements. | [alarms](#input\_alarms) | n/a |
object({
enabled = optional(bool, true)
sns_topic = string
custom_values = optional(any, {})
})
| n/a | yes | | [allocated\_storage](#input\_allocated\_storage) | The allocated storage in gigabytes | `number` | `20` | no | | [apply\_immediately](#input\_apply\_immediately) | Specifies whether any database modifications are applied immediately, or during the next maintenance window | `bool` | `false` | no | +| [aurora\_configs](#input\_aurora\_configs) | The aws rd aurora specific configurations |
object({
engine_mode = optional(string, "provisioned") # The database engine mode. Valid values: `global`, `multimaster`, `parallelquery`, `provisioned`, `serverless`(serverless is deprecated)
autoscaling_enabled = optional(bool, false) # Whether autoscaling enabled
autoscaling_min_capacity = optional(number, 0) # Min number of read replicas
autoscaling_max_capacity = optional(number, 2) # Max number of read replicas permitted
instances = optional(any, {}) # Cluster instances configs
serverlessv2_scaling_configuration = optional(any, {}) # for enabling serverless-2(the serverless-1(engine_mode=serverless, scaling_configuration is set) is deprecated), valid when `engine_mode` is set to `provisioned`
})
| `{}` | no | | [backup\_retention\_period](#input\_backup\_retention\_period) | The days to retain backups for | `number` | `35` | no | | [backup\_window](#input\_backup\_window) | The daily time range (in UTC) during which automated backups are created if they are enabled. Example: '09:46-10:16'. Must not overlap with maintenance\_window | `string` | `"03:00-06:00"` | no | | [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | The number of days to retain CloudWatch logs for the DB instance | `number` | `30` | no | @@ -112,7 +116,7 @@ No requirements. | [create\_db\_parameter\_group](#input\_create\_db\_parameter\_group) | Whether to create a database parameter group | `bool` | `false` | no | | [create\_db\_subnet\_group](#input\_create\_db\_subnet\_group) | Whether to create a database subnet group | `bool` | `true` | no | | [create\_monitoring\_role](#input\_create\_monitoring\_role) | Create IAM role with a defined name that permits RDS to send enhanced monitoring metrics to CloudWatch Logs | `bool` | `false` | no | -| [create\_security\_group](#input\_create\_security\_group) | n/a | `bool` | `false` | no | +| [create\_security\_group](#input\_create\_security\_group) | Whether to create security group and attach ingress/egress rules which will be used for rds instances(and rds proxy if we enabled it), if you already have one and do not want to create new security group you can explicitly set this variable to false and pass group id by using var.vpc\_security\_group\_ids | `bool` | `true` | no | | [db\_instance\_tags](#input\_db\_instance\_tags) | Additional tags for the DB instance | `map(any)` | `{}` | no | | [db\_name](#input\_db\_name) | The DB name to create. If omitted, no database is created initially | `string` | n/a | yes | | [db\_option\_group\_tags](#input\_db\_option\_group\_tags) | Additional tags for the DB option group | `map(any)` | `{}` | no | @@ -140,14 +144,17 @@ No requirements. | [multi\_az](#input\_multi\_az) | Specifies if the RDS instance is multi-AZ | `bool` | `true` | no | | [options](#input\_options) | A list of Options to apply | `list(any)` |
[
{
"option_name": "MARIADB_AUDIT_PLUGIN",
"option_settings": [
{
"name": "SERVER_AUDIT_EVENTS",
"value": "CONNECT"
},
{
"name": "SERVER_AUDIT_FILE_ROTATIONS",
"value": "37"
}
]
}
]
| no | | [parameter\_group\_name](#input\_parameter\_group\_name) | Name of the DB parameter group to associate or create | `string` | `"default.mysql5.7"` | no | -| [parameters](#input\_parameters) | A list of DB parameters (map) to apply | `list(map(any))` | `[]` | no | +| [parameters](#input\_parameters) | A list of DB parameters (map) to apply |
list(object({
name = string
value = string
context = optional(string, "instance") # The context where parameter will be used, supported values are "instance" and "cluster"
apply_method = optional(string, "immediate") # The apply method for parameter, supported values are "immediate" and "pending-reboot"
}))
| `[]` | no | | [port](#input\_port) | The port on which the DB accepts connections | `number` | `null` | no | +| [proxy](#input\_proxy) | The aws rds proxy specific configurations |
object({
enabled = optional(bool, false) # whether rds proxy is enabled
endpoints = optional(any, {}) # map of {: } additional proxy endpoints(by default we have already one read/write endpoint), for more info check resource doc https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_endpoint
client_auth_type = optional(string, "MYSQL_NATIVE_PASSWORD") # The type of authentication the proxy uses for connections from clients
iam_auth = optional(string, "DISABLED") # Whether IAM auth enabled
target_db_cluster = optional(bool, true) # Whether the target db is cluster
debug_logging = optional(bool, false) # Whether enhanced logging is enabled
idle_client_timeout = optional(number, 1800) # The timeout of idle connections, default is 30 minutes
})
| `{}` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Whether the database is accessible publicly. Note that if you need to enable this you have to place db on public subnets | `bool` | `false` | no | | [security\_group\_description](#input\_security\_group\_description) | n/a | `string` | `"MySQL security group"` | no | | [security\_group\_name](#input\_security\_group\_name) | n/a | `string` | `"db_security_group"` | no | +| [set\_vpc\_security\_group\_rules](#input\_set\_vpc\_security\_group\_rules) | Whether to automatically add security group rules allowing access to db from vpc network | `bool` | `true` | no | | [skip\_final\_snapshot](#input\_skip\_final\_snapshot) | Determines whether a final DB snapshot is created before the DB instance is deleted. If true is specified, no DBSnapshot is created. If false is specified, a DB snapshot is created before the DB instance is deleted | `bool` | `false` | no | | [slow\_queries](#input\_slow\_queries) | n/a |
object({
enabled = optional(bool, true)
query_duration = optional(number, 3)
})
|
{
"enabled": true,
"query_duration": 3
}
| no | | [storage\_encrypted](#input\_storage\_encrypted) | Specifies whether the DB instance is encrypted | `bool` | `false` | no | -| [storage\_type](#input\_storage\_type) | One of 'standard' (magnetic), 'gp2' (general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not | `string` | `"gp2"` | no | +| [storage\_type](#input\_storage\_type) | One of 'standard' (magnetic), 'gp2' (general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not | `string` | `null` | no | | [subnet\_ids](#input\_subnet\_ids) | A list of VPC subnet IDs | `list(string)` | n/a | yes | | [tags](#input\_tags) | A mapping of tags to assign to all resources | `map(any)` | `{}` | no | | [vpc\_id](#input\_vpc\_id) | n/a | `string` | `""` | no | diff --git a/alerts.tf b/alerts.tf index 7b6d0c9..53cd1cb 100644 --- a/alerts.tf +++ b/alerts.tf @@ -1,20 +1,3 @@ -data "aws_ec2_instance_type" "this" { - instance_type = trim(var.instance_class, "db.") -} - -data "aws_db_instance" "database" { - db_instance_identifier = var.identifier - - depends_on = [ - module.db - ] -} - -locals { - // SampleCount statistic adds 2 to the real count in case the engine is postgres, so 7 means 5 + 2 - slow_queries_alert_threshold = var.engine == "postgres" ? 7 : 5 -} - module "cw_alerts" { count = var.alarms.enabled ? 1 : 0 @@ -96,7 +79,7 @@ module "cw_alerts" { DBInstanceIdentifier = var.identifier } period = try(var.alarms.custom_values.disk.period, "300") - threshold = try(var.alarms.custom_values.disk.threshold, data.aws_db_instance.database.allocated_storage * 0.08 * 1024 * 1024 * 1024) #8% of storage in Bytes + threshold = try(var.alarms.custom_values.disk.threshold, data.aws_db_instance.database[0].allocated_storage * 0.08 * 1024 * 1024 * 1024) #8% of storage in Bytes equation = try(var.alarms.custom_values.disk.equation, "lte") statistic = try(var.alarms.custom_values.disk.statistic, "avg") }, diff --git a/data.tf b/data.tf new file mode 100644 index 0000000..b593ffd --- /dev/null +++ b/data.tf @@ -0,0 +1,20 @@ +data "aws_ec2_instance_type" "this" { + instance_type = trim(var.instance_class, "db.") +} + +data "aws_db_instance" "database" { + db_instance_identifier = var.identifier + + count = var.alarms.enabled ? 1 : 0 + + depends_on = [ + module.db, + module.db_aurora + ] +} + +data "aws_vpc" "this" { + count = var.create_security_group ? 1 : 0 + + id = var.vpc_id +} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..ac3a2dd --- /dev/null +++ b/locals.tf @@ -0,0 +1,98 @@ +locals { + vpc_security_group_ids = var.create_security_group ? [module.security_group[0].security_group_id] : var.vpc_security_group_ids + enabled_cloudwatch_logs_exports = ((var.engine == "mysql" || var.engine == "mariadb") && var.slow_queries.enabled) ? ["slowquery"] : (var.engine == "postgres" && var.slow_queries.enabled) ? ["postgresql"] : var.enabled_cloudwatch_logs_exports + # Cloudwatch log groups from which log based metrics are created in case slow queries are enabled + cloudwatch_log_groups = var.slow_queries.enabled ? { for type in local.enabled_cloudwatch_logs_exports : type => "/aws/rds/instance/${var.identifier}/${type}" } : {} + create_db_parameter_group = var.slow_queries.enabled ? true : var.create_db_parameter_group + parameter_group_name = local.create_db_parameter_group ? "${var.identifier}-${var.engine}" : null + postgres_slow_queries_duration = var.slow_queries.query_duration * 1000 + port = (endswith(var.engine, "mysql") || endswith(var.engine, "mariadb")) ? 3306 : endswith(var.engine, "postgres") ? 5432 : var.port + default_params_mysql = [ + { + name = "slow_query_log" + value = "1" + }, + { + name = "log_output" + value = "FILE" + }, + { + name = "long_query_time" + value = var.slow_queries.query_duration + }, + ] + default_params_postgres = [ + { + name = "log_min_duration_statement" //This setting causes PostgreSQL to log any query that takes longer than `local.slow_queries_duration` seconds to execute. It includes both the query text and its duration. + value = local.postgres_slow_queries_duration + }, + { + name = "log_statement" //This setting prevents the logging of every single SQL statement and logs those ones which correspond to parameter group's configuration. + value = "none" + }, + { + name = "log_duration" //When enabled, this logs the duration of every completed statement. + value = "1" + }, + ] + + # Maps from the default parameters for easier merging + params_mysql = { for p in local.default_params_mysql : p.name => p.value } + params_postgres = { for p in local.default_params_postgres : p.name => p.value } + + # Create a map from the user parameters + user_params_map = { for p in var.parameters : p.name => p.value if p.context == "instance" } + cluster_params_map = [for p in var.parameters : p if p.context == "cluster"] + + # Merge the two maps, with user parameters overriding defaults + merged_params_map = merge( + ((var.engine == "mysql" || var.engine == "mariadb") && var.slow_queries.enabled) ? local.params_mysql : {}, + (var.engine == "postgres" && var.slow_queries.enabled) ? local.params_postgres : {}, + local.user_params_map + ) + + # Convert the merged map back to a list of maps + combined_parameters = [for name, value in local.merged_params_map : { name = name, value = value }] + is_aurora = startswith(var.engine, "aurora") + engine_family = (endswith(var.engine, "mysql") || endswith(var.engine, "mariadb")) ? "MYSQL" : (endswith(var.engine, "postgres") ? "POSTGRESQL" : "") + + // SampleCount statistic adds 2 to the real count in case the engine is postgres, so 7 means 5 + 2 + slow_queries_alert_threshold = var.engine == "postgres" ? 7 : 5 + + ingress_with_cidr_blocks = concat( + var.ingress_with_cidr_blocks, + var.create_security_group && var.set_vpc_security_group_rules ? [ # make cluster available within vpc private network + { + description = "${local.port} from VPC" + from_port = local.port + to_port = local.port + protocol = "tcp" + cidr_blocks = data.aws_vpc.this[0].cidr_block + } + ] : [], + var.create_security_group && var.publicly_accessible ? [ # expose rds to public, NOTE: you need also to place instances on public subnets + { + description = "Accessible from everywhere" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = "0.0.0.0/0" + } + ] : [] + ) + + egress_with_cidr_blocks = concat( + var.egress_with_cidr_blocks, + var.create_security_group && var.set_vpc_security_group_rules && var.proxy.enabled ? [ # this egress rule needed for rds proxy + { + description = "${local.port} to VPC" + from_port = local.port + to_port = local.port + protocol = "tcp" + cidr_blocks = data.aws_vpc.this[0].cidr_block + } + ] : [], + ) + + credentials_secret_arn = try(module.db[0].db_instance_master_user_secret_arn, module.db_aurora[0].cluster_master_user_secret.secret_arn, null) +} diff --git a/main.tf b/main.tf index 382c8d1..cdd6aa2 100644 --- a/main.tf +++ b/main.tf @@ -1,66 +1,8 @@ -locals { - vpc_security_group_ids = var.create_security_group ? [module.security_group[0].security_group_id] : var.vpc_security_group_ids - enabled_cloudwatch_logs_exports = ((var.engine == "mysql" || var.engine == "mariadb") && var.slow_queries.enabled) ? ["slowquery"] : (var.engine == "postgres" && var.slow_queries.enabled) ? ["postgresql"] : var.enabled_cloudwatch_logs_exports - # Cloudwatch log groups from which log based metrics are created in case slow queries are enabled - cloudwatch_log_groups = var.slow_queries.enabled ? { for type in local.enabled_cloudwatch_logs_exports : type => "/aws/rds/instance/${var.identifier}/${type}" } : {} - create_db_parameter_group = var.slow_queries.enabled ? true : var.create_db_parameter_group - parameter_group_name = local.create_db_parameter_group ? "${var.identifier}-${var.engine}" : null - postgres_slow_queries_duration = var.slow_queries.query_duration * 1000 - port = (var.engine == "mysql" || var.engine == "mariadb") ? 3306 : var.engine == "postgres" ? 5432 : var.port - default_params_mysql = [ - { - name = "slow_query_log" - value = "1" - }, - { - name = "log_output" - value = "FILE" - }, - { - name = "long_query_time" - value = var.slow_queries.query_duration - }, - ] - default_params_postgres = [ - { - name = "log_min_duration_statement" //This setting causes PostgreSQL to log any query that takes longer than `local.slow_queries_duration` seconds to execute. It includes both the query text and its duration. - value = local.postgres_slow_queries_duration - }, - { - name = "log_statement" //This setting prevents the logging of every single SQL statement and logs those ones which correspond to parameter group's configuration. - value = "none" - }, - { - name = "log_duration" //When enabled, this logs the duration of every completed statement. - value = "1" - }, - ] - - # Maps from the default parameters for easier merging - params_mysql = { for p in local.default_params_mysql : p.name => p.value } - params_postgres = { for p in local.default_params_postgres : p.name => p.value } - - # Create a map from the user parameters - user_params_map = { for p in var.parameters : p.name => p.value } - - # Merge the two maps, with user parameters overriding defaults - merged_params_map = merge( - ((var.engine == "mysql" || var.engine == "mariadb") && var.slow_queries.enabled) ? local.params_mysql : {}, - (var.engine == "postgres" && var.slow_queries.enabled) ? local.params_postgres : {}, - local.user_params_map - ) - - # Convert the merged map back to a list of maps - combined_parameters = [for name, value in local.merged_params_map : { name = name, value = value }] -} - module "db" { source = "terraform-aws-modules/rds/aws" - version = "~> 6.1" + version = "6.10.0" - depends_on = [ - module.security_group - ] + count = local.is_aurora ? 0 : 1 identifier = var.identifier @@ -75,6 +17,7 @@ module "db" { allocated_storage = var.allocated_storage max_allocated_storage = var.max_allocated_storage storage_encrypted = var.storage_encrypted + storage_type = var.storage_type db_name = var.db_name username = var.db_username @@ -121,4 +64,112 @@ module "db" { db_parameter_group_tags = var.db_parameter_group_tags db_subnet_group_tags = var.db_subnet_group_tags manage_master_user_password = var.manage_master_user_password + publicly_accessible = var.publicly_accessible +} + +module "db_aurora" { + source = "terraform-aws-modules/rds-aurora/aws" + version = "9.11.0" + + count = local.is_aurora ? 1 : 0 + + name = var.identifier + engine = var.engine + engine_version = var.engine_version + + instance_class = var.instance_class + apply_immediately = var.apply_immediately + + allocated_storage = var.allocated_storage + storage_encrypted = var.storage_encrypted + storage_type = var.storage_type + + database_name = var.db_name + master_username = var.db_username + master_password = var.db_password + port = local.port + + subnets = var.subnet_ids + vpc_security_group_ids = local.vpc_security_group_ids + create_security_group = false # above we already create/configure/pass security group ids + + iam_database_authentication_enabled = var.iam_database_authentication_enabled + + preferred_maintenance_window = var.maintenance_window + preferred_backup_window = var.backup_window + enabled_cloudwatch_logs_exports = local.enabled_cloudwatch_logs_exports + + backup_retention_period = var.backup_retention_period + skip_final_snapshot = var.skip_final_snapshot + deletion_protection = var.deletion_protection + + create_monitoring_role = var.create_monitoring_role + monitoring_interval = var.monitoring_interval + create_cloudwatch_log_group = var.create_cloudwatch_log_group + cloudwatch_log_group_retention_in_days = var.cloudwatch_log_group_retention_in_days + + # DB instance parameter group configs + create_db_parameter_group = local.create_db_parameter_group + db_parameter_group_family = "${var.engine}${var.engine_version}" + db_parameter_group_name = local.parameter_group_name + db_parameter_group_use_name_prefix = false + db_parameter_group_description = "Custom parameter group for ${var.identifier}" + db_parameter_group_parameters = local.combined_parameters + + # DB cluster parameter group configs + create_db_cluster_parameter_group = length(local.cluster_params_map) > 0 + db_cluster_parameter_group_family = "${var.engine}${var.engine_version}" + db_cluster_parameter_group_name = "${local.parameter_group_name}-cluster" + db_cluster_parameter_group_use_name_prefix = false + db_cluster_parameter_group_description = "Custom parameter group for DB cluster ${var.identifier}" + db_cluster_parameter_group_parameters = local.cluster_params_map + + create_db_subnet_group = var.create_db_subnet_group + db_subnet_group_name = var.db_subnet_group_name + + # aurora specific configs + engine_mode = var.aurora_configs.engine_mode + autoscaling_enabled = var.aurora_configs.autoscaling_enabled + autoscaling_min_capacity = var.aurora_configs.autoscaling_min_capacity + autoscaling_max_capacity = var.aurora_configs.autoscaling_max_capacity + instances = var.aurora_configs.instances + serverlessv2_scaling_configuration = var.aurora_configs.serverlessv2_scaling_configuration + + manage_master_user_password = var.manage_master_user_password + publicly_accessible = var.publicly_accessible + + tags = var.tags + + depends_on = [ + module.security_group + ] +} + +module "proxy" { + source = "./modules/proxy" + + count = var.proxy.enabled ? 1 : 0 + + name = var.identifier + subnet_ids = var.subnet_ids + vpc_security_group_ids = local.vpc_security_group_ids + credentials_secret_arn = local.credentials_secret_arn + db_username = local.credentials_secret_arn == null ? var.db_username : null + db_password = local.credentials_secret_arn == null ? var.db_password : null + + endpoints = var.proxy.endpoints + client_auth_type = var.proxy.client_auth_type + iam_auth = var.proxy.iam_auth + target_db_cluster = var.proxy.target_db_cluster + debug_logging = var.proxy.debug_logging + + engine_family = local.engine_family + db_cluster_identifier = var.identifier + + tags = var.tags + + depends_on = [ + module.db, + module.db_aurora + ] } diff --git a/modules/proxy/README.md b/modules/proxy/README.md new file mode 100644 index 0000000..b9cd1b1 --- /dev/null +++ b/modules/proxy/README.md @@ -0,0 +1,15 @@ +## This terraform module allows to create rds cluster attached rds proxy + +### basic example + +```terraform +module "rds_proxy" { + source = "dasmeta/rds/aws//modules/proxy" + version = "1.4.0" + + name = "my-test-proxy" # in this case this will be also identifier of rds cluster + subnet_ids = ["subnet-xxxxxxxx","subnet-xxxxxx"] + vpc_security_group_ids = ["sg-xxxxxxxxx"] + credentials_secret_arn = "arn-of-secret-containing-db-username-and-password" +} +``` diff --git a/modules/proxy/main.tf b/modules/proxy/main.tf new file mode 100644 index 0000000..633af68 --- /dev/null +++ b/modules/proxy/main.tf @@ -0,0 +1,49 @@ +module "db_password" { + source = "dasmeta/modules/aws//modules/secret" + version = "2.18.2" + + count = var.credentials_secret_arn == null ? 1 : 0 + + name = "db-password-${var.name}" + recovery_window_in_days = var.credentials_secret_recovery_window + value = { + username = var.db_username + password = var.db_password + } +} + +module "this" { + source = "terraform-aws-modules/rds-proxy/aws" + version = "3.1.0" + + name = var.name + vpc_subnet_ids = var.subnet_ids + vpc_security_group_ids = var.vpc_security_group_ids + debug_logging = var.debug_logging + idle_client_timeout = var.idle_client_timeout + + endpoints = { for key, item in var.endpoints : key => merge(item, { + name = key + vpc_subnet_ids = var.subnet_ids + vpc_security_group_ids = var.vpc_security_group_ids + }) + } + + auth = { + "superuser" = { + client_password_auth_type = var.client_auth_type + iam_auth = var.iam_auth + secret_arn = try(module.db_password[0].secret_id, var.credentials_secret_arn) + } + } + + engine_family = var.engine_family + target_db_cluster = var.target_db_cluster + db_cluster_identifier = coalesce(var.db_cluster_identifier, var.name) + + tags = var.tags + + depends_on = [ + module.db_password + ] +} diff --git a/modules/proxy/output.tf b/modules/proxy/output.tf new file mode 100644 index 0000000..0869051 --- /dev/null +++ b/modules/proxy/output.tf @@ -0,0 +1,19 @@ +output "arn" { + value = module.this.proxy_arn + description = "The arn of proxy" +} + +output "id" { + value = module.this.proxy_id + description = "The id of proxy" +} + +output "endpoint" { + description = "Proxy endpoint to connect" + value = module.this.proxy_endpoint +} + +output "endpoints" { + description = "All created proxy endpoints" + value = module.this.db_proxy_endpoints +} diff --git a/modules/proxy/variables.tf b/modules/proxy/variables.tf new file mode 100644 index 0000000..654940b --- /dev/null +++ b/modules/proxy/variables.tf @@ -0,0 +1,94 @@ +variable "name" { + type = string + description = "The name/identifier of rds proxy" +} + +variable "db_cluster_identifier" { + type = string + default = null + description = "The rds db cluster name/identifier to use. If this value not passed then it will use proxy name as identifier" +} + +variable "engine_family" { + type = string + default = "MYSQL" + description = "The cluster engine family, valid values are `MYSQL` or `POSTGRESQL`" +} + +variable "credentials_secret_arn" { + type = string + default = null + description = "The aws secret manager secret arn which contains rds cluster username/password accesses, NOTE: if you do not set this you have to set db_username/db_password params" +} + +variable "credentials_secret_recovery_window" { + type = number + default = 0 + description = "The aws secret manager secret recovery window in days. If value is 0 this means the secret will be removed immediately" +} + +variable "db_username" { + type = string + default = null + description = "Username for the master DB user. NOTE: this variable should be set only in case if you do not set cluster_master_user_secret variable" +} + +variable "db_password" { + type = string + sensitive = true + default = null + description = "Password for the master DB user. NOTE: this variable should be set only in case if you do not set cluster_master_user_secret variable" +} + +variable "client_auth_type" { + type = string + default = "MYSQL_NATIVE_PASSWORD" + description = "The type of authentication the proxy uses for connections from clients" +} + +variable "iam_auth" { + type = string + default = "DISABLED" + description = "Whether IAM auth enabled for proxy" +} + +variable "target_db_cluster" { + type = bool + default = true + description = "Whether target is cluster" +} + +variable "endpoints" { + type = any + default = {} # for more info and available options check resource doc https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_endpoint + description = "map of {: } additional proxy endpoints(by default we have already one read/write endpoint), only name can be passed all other attributes are optional." +} + +variable "subnet_ids" { + type = list(string) + description = "A list of VPC subnet IDs" +} + +variable "vpc_security_group_ids" { + type = list(string) + description = "List of VPC security groups to associate" + default = [] +} + +variable "debug_logging" { + type = bool + default = false + description = "Whether the enhanced logging is enabled" +} + +variable "idle_client_timeout" { + type = number + default = 1800 + description = "The timeout of idle connections, default is 30 minutes" +} + +variable "tags" { + type = any + default = {} + description = "The tags to attach resources" +} diff --git a/security-group.tf b/security-group.tf index 6bcabe3..8e6acb0 100644 --- a/security-group.tf +++ b/security-group.tf @@ -1,15 +1,18 @@ module "security_group" { - count = var.create_security_group ? 1 : 0 - source = "terraform-aws-modules/security-group/aws" - version = "4.7.0" + version = "5.2.0" + + count = var.create_security_group ? 1 : 0 name = var.security_group_name description = var.security_group_description vpc_id = var.vpc_id # ingress - ingress_with_cidr_blocks = var.ingress_with_cidr_blocks + ingress_with_cidr_blocks = local.ingress_with_cidr_blocks + + # egress + egress_with_cidr_blocks = local.egress_with_cidr_blocks tags = var.tags } diff --git a/tests/basic-aurora-mysql-and-proxy/0-setup.tf b/tests/basic-aurora-mysql-and-proxy/0-setup.tf new file mode 100644 index 0000000..54082f0 --- /dev/null +++ b/tests/basic-aurora-mysql-and-proxy/0-setup.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "eu-central-1" +} + +# get region default vpc and its subnets +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "default" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} diff --git a/tests/basic-aurora-mysql-and-proxy/1-example.tf b/tests/basic-aurora-mysql-and-proxy/1-example.tf new file mode 100644 index 0000000..9c30e56 --- /dev/null +++ b/tests/basic-aurora-mysql-and-proxy/1-example.tf @@ -0,0 +1,50 @@ +module "this" { + source = "../.." + + engine = "aurora-mysql" + engine_version = "8.0" + instance_class = "db.t3.medium" + identifier = "test-basic-aurora-mysql-and-proxy" + db_name = "testDb" + db_username = "testUser" + db_password = "" + allocated_storage = null + publicly_accessible = true + skip_final_snapshot = true + apply_immediately = true + + vpc_id = data.aws_vpc.default.id + subnet_ids = data.aws_subnets.default.ids + + create_db_parameter_group = true + parameters = [ + { "name" : "sql_mode", "value" : "NO_ENGINE_SUBSTITUTION" }, + { "name" : "character_set_server", "value" : "utf8mb4", context = "cluster" }, + { "name" : "collation_server", "value" : "utf8mb4_general_ci", context = "cluster" }, + { "name" : "event_scheduler", "value" : "ON", context = "cluster" } + ] + + slow_queries = { "enabled" : false, "query_duration" : 1 } + + create_security_group = true + security_group_name = "test-basic-aurora-mysql-and-proxy-sg" + + aurora_configs = { + autoscaling_enabled = true + autoscaling_min_capacity = 1 + autoscaling_max_capacity = 2 + + instances = { # at least one master instance needs to be created + master = {} + } + } + + proxy = { + enabled = true + } + + alarms = { + enabled = false + sns_topic = "" + } +} diff --git a/tests/basic-aurora-mysql-and-proxy/README.md b/tests/basic-aurora-mysql-and-proxy/README.md new file mode 100644 index 0000000..2e24c21 --- /dev/null +++ b/tests/basic-aurora-mysql-and-proxy/README.md @@ -0,0 +1,36 @@ +# base + + +## Requirements + +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../.. | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_subnets.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnets) | data source | +| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/tests/basic-aurora-mysql/0-setup.tf b/tests/basic-aurora-mysql/0-setup.tf new file mode 100644 index 0000000..54082f0 --- /dev/null +++ b/tests/basic-aurora-mysql/0-setup.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "eu-central-1" +} + +# get region default vpc and its subnets +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "default" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} diff --git a/tests/basic-aurora-mysql/1-example.tf b/tests/basic-aurora-mysql/1-example.tf new file mode 100644 index 0000000..6eb60ea --- /dev/null +++ b/tests/basic-aurora-mysql/1-example.tf @@ -0,0 +1,46 @@ +module "this" { + source = "../.." + + engine = "aurora-mysql" + engine_version = "8.0" + instance_class = "db.t3.medium" + identifier = "test-basic-aurora-mysql" + db_name = "testDb" + db_username = "testUser" + db_password = "" + allocated_storage = null + publicly_accessible = true + skip_final_snapshot = true + apply_immediately = true + + vpc_id = data.aws_vpc.default.id + subnet_ids = data.aws_subnets.default.ids + + create_db_parameter_group = true + parameters = [ + { "name" : "sql_mode", "value" : "NO_ENGINE_SUBSTITUTION" }, + { "name" : "character_set_server", "value" : "utf8mb4", context = "cluster" }, + { "name" : "collation_server", "value" : "utf8mb4_general_ci", context = "cluster" }, + { "name" : "event_scheduler", "value" : "ON", context = "cluster" } + ] + + slow_queries = { "enabled" : false, "query_duration" : 1 } + + create_security_group = true + security_group_name = "test-basic-aurora-mysql-sg" + + aurora_configs = { + autoscaling_enabled = true + autoscaling_min_capacity = 1 + autoscaling_max_capacity = 2 + + instances = { # at least one master instance needs to be created + master = {} + } + } + + alarms = { + enabled = false + sns_topic = "" + } +} diff --git a/tests/basic-aurora-mysql/README.md b/tests/basic-aurora-mysql/README.md new file mode 100644 index 0000000..2e24c21 --- /dev/null +++ b/tests/basic-aurora-mysql/README.md @@ -0,0 +1,36 @@ +# base + + +## Requirements + +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../.. | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_subnets.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnets) | data source | +| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/variables.tf b/variables.tf index 18898d7..32e1a5a 100644 --- a/variables.tf +++ b/variables.tf @@ -8,7 +8,6 @@ variable "security_group_name" { default = "db_security_group" } - variable "alarms" { type = object({ enabled = optional(bool, true) @@ -49,9 +48,15 @@ variable "egress_with_cidr_blocks" { default = [] } +variable "set_vpc_security_group_rules" { + type = bool + default = true + description = "Whether to automatically add security group rules allowing access to db from vpc network" +} + variable "storage_type" { type = string - default = "gp2" + default = null description = "One of 'standard' (magnetic), 'gp2' (general purpose SSD), or 'io1' (provisioned IOPS SSD). The default is 'io1' if iops is specified, 'gp2' if not" } @@ -81,7 +86,7 @@ variable "major_engine_version" { variable "instance_class" { type = string - default = "db.t3.micro" + default = "db.t3.micro" # for aurora-mysql>=3.x(mysql>=8.x) min instance class is "db.t3.medium", check the docs for supported instance classes: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.DBInstanceClass.SupportAurora.html description = "The instance type of the RDS instance" } @@ -115,6 +120,7 @@ variable "db_username" { variable "db_password" { type = string + sensitive = true description = "Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file" } @@ -196,7 +202,12 @@ variable "monitoring_role_name" { } variable "parameters" { - type = list(map(any)) + type = list(object({ + name = string + value = string + context = optional(string, "instance") # The context where parameter will be used, supported values are "instance" and "cluster" + apply_method = optional(string, "immediate") # The apply method for parameter, supported values are "immediate" and "pending-reboot" + })) default = [] description = "A list of DB parameters (map) to apply" } @@ -256,8 +267,9 @@ variable "db_subnet_group_use_name_prefix" { } variable "create_security_group" { - type = bool - default = false + type = bool + default = true + description = "Whether to create security group and attach ingress/egress rules which will be used for rds instances(and rds proxy if we enabled it), if you already have one and do not want to create new security group you can explicitly set this variable to false and pass group id by using var.vpc_security_group_ids" } variable "create_db_parameter_group" { @@ -312,3 +324,36 @@ variable "slow_queries" { query_duration = 3 } } + +variable "publicly_accessible" { + type = bool + default = false + description = "Whether the database is accessible publicly. Note that if you need to enable this you have to place db on public subnets" +} + +variable "aurora_configs" { + type = object({ + engine_mode = optional(string, "provisioned") # The database engine mode. Valid values: `global`, `multimaster`, `parallelquery`, `provisioned`, `serverless`(serverless is deprecated) + autoscaling_enabled = optional(bool, false) # Whether autoscaling enabled + autoscaling_min_capacity = optional(number, 0) # Min number of read replicas + autoscaling_max_capacity = optional(number, 2) # Max number of read replicas permitted + instances = optional(any, {}) # Cluster instances configs + serverlessv2_scaling_configuration = optional(any, {}) # for enabling serverless-2(the serverless-1(engine_mode=serverless, scaling_configuration is set) is deprecated), valid when `engine_mode` is set to `provisioned` + }) + default = {} + description = "The aws rd aurora specific configurations" +} + +variable "proxy" { + type = object({ + enabled = optional(bool, false) # whether rds proxy is enabled + endpoints = optional(any, {}) # map of {: } additional proxy endpoints(by default we have already one read/write endpoint), for more info check resource doc https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_endpoint + client_auth_type = optional(string, "MYSQL_NATIVE_PASSWORD") # The type of authentication the proxy uses for connections from clients + iam_auth = optional(string, "DISABLED") # Whether IAM auth enabled + target_db_cluster = optional(bool, true) # Whether the target db is cluster + debug_logging = optional(bool, false) # Whether enhanced logging is enabled + idle_client_timeout = optional(number, 1800) # The timeout of idle connections, default is 30 minutes + }) + default = {} + description = "The aws rds proxy specific configurations" +}