diff --git a/README.md b/README.md index 4df2ed84..1086c87a 100644 --- a/README.md +++ b/README.md @@ -89,20 +89,20 @@ You need the following permissions to run this module. | [auto\_scaling](#input\_auto\_scaling) | Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-autoscaling&interface=cli#autoscaling-considerations in the IBM Cloud Docs. |
object({
disk = object({
capacity_enabled = optional(bool, false)
free_space_less_than_percent = optional(number, 10)
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 3670016)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
memory = object({
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 114688)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
})
| `null` | no | | [backup\_crn](#input\_backup\_crn) | The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty. | `string` | `null` | no | | [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | -| [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of CBR rules to create |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
}))
| `[]` | no | +| [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of context-based restrictions rules to create. |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})))
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
}))
| `[]` | no | | [cpu\_count](#input\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `0` | no | -| [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `10240` | no | -| [endpoints](#input\_endpoints) | Specify whether you want to enable the public, private, or both service endpoints. Supported values are 'public', 'private', or 'public-and-private'. | `string` | `"private"` | no | -| [instance\_name](#input\_instance\_name) | The name to give the MongoDB instance. | `string` | n/a | yes | +| [disk\_mb](#input\_disk\_mb) | The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `10240` | no | | [kms\_key\_crn](#input\_kms\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [member\_host\_flavor](#input\_member\_host\_flavor) | Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor). | `string` | `null` | no | -| [members](#input\_members) | Allocated number of members | `number` | `3` | no | +| [members](#input\_members) | The number of members that are allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-resources-scaling) | `number` | `3` | no | | [memory\_mb](#input\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `4096` | no | | [mongodb\_version](#input\_mongodb\_version) | The version of the MongoDB to provision. If no value passed, the current ICD preferred version is used. For our version policy, see https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-versioning-policy for more details | `string` | `null` | no | +| [name](#input\_name) | The name to give the MongoDB instance. | `string` | n/a | yes | | [plan](#input\_plan) | The name of the service plan that you choose for your MongoDB instance | `string` | `"standard"` | no | | [region](#input\_region) | The region where you want to deploy your instance. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the MongoDB instance will be created. | `string` | n/a | yes | | [service\_credential\_names](#input\_service\_credential\_names) | Map of name, role for service credentials that you want to create for the database | `map(string)` | `{}` | no | +| [service\_endpoints](#input\_service\_endpoints) | The type of endpoint of the database instance. Possible values: `public`, `private`, `public-and-private`. | `string` | `"public"` | no | | [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of IAM authorization policies that permits all Databases for MongoDB instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key that was provided in the `kms_key_crn` and `backup_encryption_key_crn` inputs. This policy is required in order to enable KMS encryption, so only skip creation if there is one already present in your account. No policy is created if `use_ibm_owned_encryption_key` is true. | `bool` | `false` | no | | [tags](#input\_tags) | Optional list of tags to be added to the MongoDB instance. | `list(any)` | `[]` | no | | [use\_default\_backup\_encryption\_key](#input\_use\_default\_backup\_encryption\_key) | When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `kms_key_crn`, or in `backup_encryption_key_crn` if a value is passed. If you do not want to use your own key for backups encryption, you can set this to `true` to use the IBM Cloud Databases default encryption for backups. Alternatively set `use_ibm_owned_encryption_key` to true to use the default encryption for both backups and deployment data. | `bool` | `false` | no | diff --git a/examples/backup-restore/catalogValidationValues.json.template b/examples/backup-restore/catalogValidationValues.json.template new file mode 100644 index 00000000..8e580fd3 --- /dev/null +++ b/examples/backup-restore/catalogValidationValues.json.template @@ -0,0 +1,6 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY, + "region": "us-south", + "tags": $TAGS, + "prefix": $PREFIX +} diff --git a/examples/backup-restore/main.tf b/examples/backup-restore/main.tf index a8ef60a7..3ba26429 100644 --- a/examples/backup-restore/main.tf +++ b/examples/backup-restore/main.tf @@ -11,18 +11,18 @@ module "resource_group" { } data "ibm_database_backups" "backup_database" { - deployment_id = var.mongo_db_crn + deployment_id = var.existing_database_crn } # New mongo db instance pointing to the backup instance -module "restored_mongo_db" { +module "restored_icd_mongodb" { source = "../.." resource_group_id = module.resource_group.resource_group_id - instance_name = "${var.prefix}-mongodb-restored" + name = "${var.prefix}-mongodb-restored" region = var.region mongodb_version = var.mongodb_version access_tags = var.access_tags - tags = var.resource_tags + tags = var.tags member_host_flavor = "multitenant" backup_crn = data.ibm_database_backups.backup_database.backups[0].backup_id } diff --git a/examples/backup-restore/outputs.tf b/examples/backup-restore/outputs.tf index 0bead878..62782864 100644 --- a/examples/backup-restore/outputs.tf +++ b/examples/backup-restore/outputs.tf @@ -2,12 +2,12 @@ # Outputs ############################################################################## -output "restored_mongo_db_id" { +output "restored_icd_mongodb_id" { description = "Restored MongoDB instance id" - value = module.restored_mongo_db.id + value = module.restored_icd_mongodb.id } -output "restored_mongo_db_version" { +output "restored_icd_mongodb_version" { description = "Restored MongoDB instance version" - value = module.restored_mongo_db.version + value = module.restored_icd_mongodb.version } diff --git a/examples/backup-restore/variables.tf b/examples/backup-restore/variables.tf index 7274dec7..a966b259 100644 --- a/examples/backup-restore/variables.tf +++ b/examples/backup-restore/variables.tf @@ -34,13 +34,14 @@ variable "mongodb_version" { default = null } -variable "resource_tags" { +variable "tags" { type = list(string) description = "Optional list of tags to be added to created resources" default = [] } -variable "mongo_db_crn" { +variable "existing_database_crn" { type = string description = "The existing CRN of a mongoDB instance to fetch the latest backup crn." + default = null } diff --git a/examples/basic/main.tf b/examples/basic/main.tf index c938533a..3830299a 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -11,15 +11,23 @@ module "resource_group" { } ############################################################################## -# ICD mongodb database +# MongoDB Instance ############################################################################## -module "mongodb" { - source = "../.." - resource_group_id = module.resource_group.resource_group_id - instance_name = "${var.prefix}-mongodb" - region = var.region - mongodb_version = var.mongodb_version - access_tags = var.access_tags - tags = var.resource_tags +module "database" { + source = "../.." + resource_group_id = module.resource_group.resource_group_id + name = "${var.prefix}-data-store" + region = var.region + mongodb_version = var.mongodb_version + access_tags = var.access_tags + tags = var.tags + service_endpoints = var.service_endpoints + member_host_flavor = var.member_host_flavor + service_credential_names = { + "mongodb_admin" : "Administrator", + "mongodb_operator" : "Operator", + "mongodb_viewer" : "Viewer", + "mongodb_editor" : "Editor", + } } diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index 7807711a..1a033244 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -2,32 +2,37 @@ # Outputs ############################################################################## output "id" { - description = "Mongodb instance id" - value = module.mongodb.id + description = "Database instance id" + value = module.database.id +} + +output "mongodb_crn" { + description = "Mongodb CRN" + value = module.database.crn } output "version" { description = "Mongodb instance version" - value = module.mongodb.version + value = module.database.version } output "adminuser" { description = "Database admin user name" - value = module.mongodb.adminuser + value = module.database.adminuser } output "hostname" { description = "Database connection hostname" - value = module.mongodb.hostname + value = module.database.hostname } output "port" { description = "Database connection port" - value = module.mongodb.port + value = module.database.port } output "certificate_base64" { description = "Database connection certificate" - value = module.mongodb.certificate_base64 + value = module.database.certificate_base64 sensitive = true } diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index 692e3603..b07891a9 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -34,8 +34,25 @@ variable "mongodb_version" { default = null } -variable "resource_tags" { +variable "tags" { type = list(string) description = "Optional list of tags to be added to created resources" default = [] } + +variable "service_endpoints" { + type = string + description = "The type of endpoint of the database instance. Possible values: `public`, `private`, `public-and-private`." + default = "public" + + validation { + condition = can(regex("public|public-and-private|private", var.service_endpoints)) + error_message = "Valid values for service_endpoints are 'public', 'public-and-private', and 'private'" + } +} +variable "member_host_flavor" { + type = string + description = "The host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." + default = "multitenant" + # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. +} diff --git a/examples/basic/version.tf b/examples/basic/version.tf index bd949ed6..d4e885b2 100644 --- a/examples/basic/version.tf +++ b/examples/basic/version.tf @@ -6,5 +6,6 @@ terraform { source = "IBM-Cloud/ibm" version = "1.70.0" } + } } diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 5c7fbea0..b2de04ff 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -1,3 +1,18 @@ +############################################################################## +# Locals +############################################################################## + +locals { + secret_manager_guid = var.existing_secret_manager_instance_guid == null ? module.secrets_manager[0].secrets_manager_guid : var.existing_secret_manager_instance_guid + secret_manager_region = var.existing_secret_manager_instance_region == null ? var.region : var.existing_secret_manager_instance_region + service_credential_names = { + "es_admin" : "Administrator", + "es_operator" : "Operator", + "es_viewer" : "Viewer", + "es_editor" : "Editor", + } +} + ############################################################################## # Resource Group ############################################################################## @@ -16,7 +31,7 @@ module "resource_group" { resource "ibm_is_vpc" "example_vpc" { name = "${var.prefix}-vpc" resource_group = module.resource_group.resource_group_id - tags = var.resource_tags + tags = var.tags } resource "ibm_is_subnet" "testacc_subnet" { @@ -40,11 +55,11 @@ module "key_protect_all_inclusive" { source = "terraform-ibm-modules/kms-all-inclusive/ibm" version = "4.21.6" resource_group_id = module.resource_group.resource_group_id - # Note: Database instance and Key Protect must be created in the same region when using BYOK - # See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok + # Only us-south, us-east and eu-de backup encryption keys are supported. See https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok for details. + # Note: Database instance and Key Protect must be created on the same region. region = var.region key_protect_instance_name = "${var.prefix}-kp" - resource_tags = var.resource_tags + resource_tags = var.tags keys = [ { key_ring_name = "icd" @@ -85,47 +100,83 @@ module "cbr_zone" { } ############################################################################## -# ICD MongoDB instance +# MongoDB Instance ############################################################################## -module "mongodb" { +module "icd_mongodb" { source = "../.." resource_group_id = module.resource_group.resource_group_id + name = "${var.prefix}-mongodb" mongodb_version = var.mongodb_version - instance_name = "${var.prefix}-mongodb" admin_pass = var.admin_pass users = var.users region = var.region + plan = var.plan access_tags = var.access_tags - tags = var.resource_tags + tags = var.tags + auto_scaling = var.auto_scaling # Example of how to use different KMS keys for data and backups use_ibm_owned_encryption_key = false use_same_kms_key_for_backups = false kms_key_crn = module.key_protect_all_inclusive.keys["icd.${local.data_key_name}"].crn backup_encryption_key_crn = module.key_protect_all_inclusive.keys["icd.${local.backups_key_name}"].crn - service_credential_names = { - "mongodb_admin" : "Administrator", - "mongodb_operator" : "Operator", - "mongodb_viewer" : "Viewer", - "mongodb_editor" : "Editor", - } - member_host_flavor = "multitenant" - cbr_rules = [ - { - description = "${var.prefix}-mongodb access only from vpc" - enforcement_mode = "enabled" - account_id = data.ibm_iam_account_settings.iam_account_settings.account_id - rule_contexts = [{ - attributes = [ - { - "name" : "endpointType", - "value" : "private" - }, - { - name = "networkZoneId" - value = module.cbr_zone.zone_id - }] - }] - } - ] + service_credential_names = local.service_credential_names + member_host_flavor = "multitenant" + memory_mb = 4096 +} + +############################################################################## +## Secrets Manager layer +############################################################################## + +# Create Secrets Manager Instance (if not using existing one) +module "secrets_manager" { + count = var.existing_secret_manager_instance_guid == null ? 1 : 0 + source = "terraform-ibm-modules/secrets-manager/ibm" + version = "1.23.6" + resource_group_id = module.resource_group.resource_group_id + region = var.region + secrets_manager_name = "${var.prefix}-secrets-manager" + sm_service_plan = "trial" + allowed_network = "public-and-private" + sm_tags = var.tags +} + +# Add a Secrets Group to the secret manager instance +module "secrets_manager_secrets_group" { + source = "terraform-ibm-modules/secrets-manager-secret-group/ibm" + version = "1.2.2" + region = local.secret_manager_region + secrets_manager_guid = local.secret_manager_guid + #tfsec:ignore:general-secrets-no-plaintext-exposure + secret_group_name = "${var.prefix}-es-secrets" + secret_group_description = "service secret-group" #tfsec:ignore:general-secrets-no-plaintext-exposure +} + +# Add service credentials to secret manager as a username/password secret type in the created secret group +module "secrets_manager_service_credentials_user_pass" { + source = "terraform-ibm-modules/secrets-manager-secret/ibm" + version = "1.6.0" + for_each = local.service_credential_names + region = local.secret_manager_region + secrets_manager_guid = local.secret_manager_guid + secret_group_id = module.secrets_manager_secrets_group.secret_group_id + secret_name = "${var.prefix}-${each.key}-credentials" + secret_description = "MongoDB Service Credentials for ${each.key}" + secret_username = module.icd_mongodb.service_credentials_object.credentials[each.key].username + secret_payload_password = module.icd_mongodb.service_credentials_object.credentials[each.key].password + secret_type = "username_password" #checkov:skip=CKV_SECRET_6 +} + +# Add MongoDB certificate to secret manager as a certificate secret type in the created secret group. +module "secrets_manager_service_credentials_cert" { + source = "terraform-ibm-modules/secrets-manager-secret/ibm" + version = "1.6.0" + region = local.secret_manager_region + secrets_manager_guid = local.secret_manager_guid + secret_group_id = module.secrets_manager_secrets_group.secret_group_id + secret_name = "${var.prefix}-es-cert" + secret_description = "MongoDB Service Credential Certificate" + imported_cert_certificate = base64decode(module.icd_mongodb.service_credentials_object.certificate) + secret_type = "imported_cert" #checkov:skip=CKV_SECRET_6 } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index a74855f0..3df4d2e6 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -4,37 +4,42 @@ output "id" { description = "MongoDB instance id" - value = module.mongodb.id + value = module.icd_mongodb.id } output "version" { description = "MongoDB instance version" - value = module.mongodb.version + value = module.icd_mongodb.version } output "guid" { description = "mongodb instance guid" - value = module.mongodb.guid + value = module.icd_mongodb.guid +} + +output "crn" { + description = "MongoDB instance crn" + value = module.icd_mongodb.crn } output "service_credentials_json" { description = "Service credentials json map" - value = module.mongodb.service_credentials_json + value = module.icd_mongodb.service_credentials_json sensitive = true } output "service_credentials_object" { description = "Service credentials object" - value = module.mongodb.service_credentials_object + value = module.icd_mongodb.service_credentials_object sensitive = true } output "hostname" { description = "MongoDB instance hostname" - value = module.mongodb.hostname + value = module.icd_mongodb.hostname } output "port" { description = "MongoDB instance port" - value = module.mongodb.port + value = module.icd_mongodb.port } diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 6171e76e..b8738772 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -1,3 +1,4 @@ + variable "ibmcloud_api_key" { type = string description = "The IBM Cloud API Key" @@ -22,7 +23,7 @@ variable "resource_group" { default = null } -variable "resource_tags" { +variable "tags" { type = list(string) description = "Optional list of tags to be added to created resources" default = [] @@ -40,6 +41,24 @@ variable "mongodb_version" { default = null } +variable "plan" { + type = string + description = "The name of the service plan that you choose for your MongoDB instance" + default = "enterprise" +} + +variable "existing_secret_manager_instance_guid" { + type = string + description = "Existing Secrets Manager GUID. If not provided an new instance will be provisioned" + default = null +} + +variable "existing_secret_manager_instance_region" { + type = string + description = "Required if value is passed into var.existing_secret_manager_instance_guid" + default = null +} + variable "admin_pass" { type = string default = null @@ -58,3 +77,38 @@ variable "users" { sensitive = true description = "A list of users that you want to create on the database. Multiple blocks are allowed. The user password must be in the range of 10-32 characters." } + +variable "auto_scaling" { + type = object({ + disk = object({ + capacity_enabled = optional(bool) + free_space_less_than_percent = optional(number) + io_above_percent = optional(number) + io_enabled = optional(bool) + io_over_period = optional(string) + rate_increase_percent = optional(number) + rate_limit_mb_per_member = optional(number) + rate_period_seconds = optional(number) + rate_units = optional(string) + }) + memory = object({ + io_above_percent = optional(number) + io_enabled = optional(bool) + io_over_period = optional(string) + rate_increase_percent = optional(number) + rate_limit_mb_per_member = optional(number) + rate_period_seconds = optional(number) + rate_units = optional(string) + }) + }) + description = "(Optional) Configure rules to allow your database to automatically increase its resources. Single block of autoscaling is allowed at once." + default = { + disk = { + capacity_enabled : true, + io_enabled : true + } + memory = { + io_enabled : true, + } + } +} diff --git a/examples/fscloud/main.tf b/examples/fscloud/main.tf index 680331fb..8ef150d7 100644 --- a/examples/fscloud/main.tf +++ b/examples/fscloud/main.tf @@ -23,7 +23,7 @@ data "ibm_iam_account_settings" "iam_account_settings" { resource "ibm_is_vpc" "example_vpc" { name = "${var.prefix}-vpc" resource_group = module.resource_group.resource_group_id - tags = var.resource_tags + tags = var.tags } resource "ibm_is_subnet" "testacc_subnet" { @@ -56,9 +56,9 @@ module "cbr_zone" { module "mongodb" { source = "../../modules/fscloud" resource_group_id = module.resource_group.resource_group_id - instance_name = "${var.prefix}-mongodb" + name = "${var.prefix}-mongodb" region = var.region - tags = var.resource_tags + tags = var.tags access_tags = var.access_tags kms_key_crn = var.kms_key_crn backup_encryption_key_crn = var.backup_encryption_key_crn @@ -70,15 +70,7 @@ module "mongodb" { "mongodb_viewer" : "Viewer", "mongodb_editor" : "Editor", } - auto_scaling = { - disk = { - capacity_enabled : true, - io_enabled : true - } - memory = { - io_enabled : true, - } - } + auto_scaling = var.auto_scaling member_host_flavor = "b3c.4x16.encrypted" cbr_rules = [ { diff --git a/examples/fscloud/variables.tf b/examples/fscloud/variables.tf index 61edf7d9..4fb1e67e 100644 --- a/examples/fscloud/variables.tf +++ b/examples/fscloud/variables.tf @@ -22,7 +22,7 @@ variable "resource_group" { default = null } -variable "resource_tags" { +variable "tags" { type = list(string) description = "Optional list of tags to be added to created resources" default = [] @@ -57,3 +57,38 @@ variable "backup_encryption_key_crn" { default = null # Validation happens in the root module } + +variable "auto_scaling" { + type = object({ + disk = object({ + capacity_enabled = optional(bool) + free_space_less_than_percent = optional(number) + io_above_percent = optional(number) + io_enabled = optional(bool) + io_over_period = optional(string) + rate_increase_percent = optional(number) + rate_limit_mb_per_member = optional(number) + rate_period_seconds = optional(number) + rate_units = optional(string) + }) + memory = object({ + io_above_percent = optional(number) + io_enabled = optional(bool) + io_over_period = optional(string) + rate_increase_percent = optional(number) + rate_limit_mb_per_member = optional(number) + rate_period_seconds = optional(number) + rate_units = optional(string) + }) + }) + description = "Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/databases-for-elasticsearch?topic=databases-for-elasticsearch-autoscaling&interface=cli#autoscaling-considerations in the IBM Cloud Docs." + default = { + disk = { + capacity_enabled : true, + io_enabled : true + } + memory = { + io_enabled : true, + } + } +} diff --git a/examples/fscloud/version.tf b/examples/fscloud/version.tf index 05dee1cd..b4448636 100644 --- a/examples/fscloud/version.tf +++ b/examples/fscloud/version.tf @@ -4,7 +4,7 @@ terraform { # Use latest version of provider in non-basic examples to verify latest version works with module ibm = { source = "IBM-Cloud/ibm" - version = ">=1.70.0, <2.0.0" + version = ">=1.70.0" } } } diff --git a/ibm_catalog.json b/ibm_catalog.json index bb62b52e..38bda124 100644 --- a/ibm_catalog.json +++ b/ibm_catalog.json @@ -208,13 +208,13 @@ "key": "members" }, { - "key": "member_memory_mb" + "key": "memory_mb" }, { - "key": "member_cpu_count" + "key": "cpu_count" }, { - "key": "member_disk_mb" + "key": "disk_mb" }, { "key": "member_host_flavor" @@ -225,6 +225,18 @@ { "key": "admin_pass" }, + { + "key": "admin_pass_secret_manager_secret_group" + }, + { + "key": "admin_pass_secret_manager_secret_name" + }, + { + "key": "existing_mongodb_instance_crn" + }, + { + "key": "use_existing_admin_pass_secret_manager_secret_group" + }, { "key": "users" }, @@ -280,7 +292,7 @@ "key": "service_credential_secrets" }, { - "key": "skip_mongodb_sm_auth_policy" + "key": "skip_mongodb_secret_manager_auth_policy" } ] } diff --git a/main.tf b/main.tf index 3f9f4481..3c840b0c 100644 --- a/main.tf +++ b/main.tf @@ -12,9 +12,13 @@ locals { validate_backup_key = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn != null && (var.use_default_backup_encryption_key || var.use_same_kms_key_for_backups) ? tobool("When passing a value for 'backup_encryption_key_crn' you cannot set 'use_default_backup_encryption_key' to true or 'use_ibm_owned_encryption_key' to false.") : true # tflint-ignore: terraform_unused_declarations validate_backup_key_2 = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn == null && !var.use_same_kms_key_for_backups ? tobool("When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'.") : true +} - # If no value passed for 'backup_encryption_key_crn' use the value of 'kms_key_crn' and perform validation of 'kms_key_crn' to check if region is supported by backup encryption key. +######################################################################################################################## +# Locals +######################################################################################################################## +locals { # If 'use_ibm_owned_encryption_key' is true or 'use_default_backup_encryption_key' is true, default to null. # If no value is passed for 'backup_encryption_key_crn', then default to use 'kms_key_crn'. backup_encryption_key_crn = var.use_ibm_owned_encryption_key || var.use_default_backup_encryption_key ? null : (var.backup_encryption_key_crn != null ? var.backup_encryption_key_crn : var.kms_key_crn) @@ -113,9 +117,8 @@ resource "ibm_iam_authorization_policy" "kms_policy" { # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 resource "time_sleep" "wait_for_authorization_policy" { - count = local.create_kms_auth_policy - depends_on = [ibm_iam_authorization_policy.kms_policy] - + count = local.create_kms_auth_policy + depends_on = [ibm_iam_authorization_policy.kms_policy] create_duration = "30s" } @@ -169,8 +172,8 @@ resource "time_sleep" "wait_for_backup_kms_authorization_policy" { ######################################################################################################################## resource "ibm_database" "mongodb" { - depends_on = [time_sleep.wait_for_authorization_policy] - name = var.instance_name + depends_on = [time_sleep.wait_for_authorization_policy, time_sleep.wait_for_backup_kms_authorization_policy] + name = var.name location = var.region plan = var.plan service = "databases-for-mongodb" @@ -178,7 +181,7 @@ resource "ibm_database" "mongodb" { resource_group_id = var.resource_group_id adminpassword = var.admin_pass tags = var.tags - service_endpoints = var.endpoints + service_endpoints = var.service_endpoints key_protect_key = var.kms_key_crn backup_encryption_key_crn = local.backup_encryption_key_crn backup_id = var.backup_crn @@ -327,7 +330,7 @@ module "cbr_rule" { }, { name = "serviceInstance" - value = ibm_database.mongodb.guid + value = ibm_database.mongodb.id operator = "stringEquals" }, { @@ -380,7 +383,7 @@ locals { } data "ibm_database_connection" "database_connection" { - endpoint_type = var.endpoints == "public-and-private" ? "public" : var.endpoints + endpoint_type = var.service_endpoints == "public-and-private" ? "public" : var.service_endpoints deployment_id = ibm_database.mongodb.id user_id = ibm_database.mongodb.adminuser user_type = "database" diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index 58919f2d..b1dd1548 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -34,15 +34,15 @@ No resources. | [auto\_scaling](#input\_auto\_scaling) | Optional rules to allow the database to increase resources in response to usage. Only a single autoscaling block is allowed. Make sure you understand the effects of autoscaling, especially for production environments. See https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-autoscaling&interface=cli#autoscaling-considerations in the IBM Cloud Docs. |
object({
disk = object({
capacity_enabled = optional(bool, false)
free_space_less_than_percent = optional(number, 10)
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 3670016)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
memory = object({
io_above_percent = optional(number, 90)
io_enabled = optional(bool, false)
io_over_period = optional(string, "15m")
rate_increase_percent = optional(number, 10)
rate_limit_mb_per_member = optional(number, 114688)
rate_period_seconds = optional(number, 900)
rate_units = optional(string, "mb")
})
})
| `null` | no | | [backup\_crn](#input\_backup\_crn) | The CRN of a backup resource to restore from. The backup is created by a database deployment with the same service ID. The backup is loaded after provisioning and the new deployment starts up that uses that data. A backup CRN is in the format crn:v1:<…>:backup:. If omitted, the database is provisioned empty. | `string` | `null` | no | | [backup\_encryption\_key\_crn](#input\_backup\_encryption\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key that you want to use for encrypting the disk that holds deployment backups. Applies only if `use_ibm_owned_encryption_key` is false and `use_same_kms_key_for_backups` is false. If no value is passed, and `use_same_kms_key_for_backups` is true, the value of `kms_key_crn` is used. Alternatively set `use_default_backup_encryption_key` to true to use the IBM Cloud Databases default encryption. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | -| [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of CBR rules to create |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
}))
| `[]` | no | +| [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of context-based restrictions rules to create. |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})))
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
}))
| `[]` | no | | [cpu\_count](#input\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `6` | no | | [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `20480` | no | -| [instance\_name](#input\_instance\_name) | Name of the mongodb instance | `string` | n/a | yes | | [kms\_key\_crn](#input\_kms\_key\_crn) | The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. Applies only if `use_ibm_owned_encryption_key` is false. By default this key is used for both deployment data and backups, but this behaviour can be altered using the `use_same_kms_key_for_backups` and `backup_encryption_key_crn` inputs. Bare in mind that backups encryption is only available in certain regions. See [Bring your own key for backups](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-key-protect&interface=ui#key-byok) and [Using the HPCS Key for Backup encryption](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-hpcs#use-hpcs-backups). | `string` | `null` | no | | [member\_host\_flavor](#input\_member\_host\_flavor) | Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor). | `string` | `null` | no | | [members](#input\_members) | Allocated number of members | `number` | `3` | no | | [memory\_mb](#input\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member) | `number` | `14336` | no | | [mongodb\_version](#input\_mongodb\_version) | Version of the MongoDB instance. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | +| [name](#input\_name) | Name of the mongodb instance | `string` | n/a | yes | | [plan](#input\_plan) | The name of the service plan that you choose for your MongoDB instance | `string` | `"enterprise"` | no | | [region](#input\_region) | The region where you want to deploy your instance. Must be the same region as the Hyper Protect Crypto Services instance. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the MongoDB instance will be created. | `string` | n/a | yes | diff --git a/modules/fscloud/main.tf b/modules/fscloud/main.tf index a1c57152..2200f38c 100644 --- a/modules/fscloud/main.tf +++ b/modules/fscloud/main.tf @@ -1,10 +1,11 @@ module "mongodb" { source = "../../" resource_group_id = var.resource_group_id - instance_name = var.instance_name + name = var.name region = var.region + plan = var.plan skip_iam_authorization_policy = var.skip_iam_authorization_policy - endpoints = "private" + service_endpoints = "private" mongodb_version = var.mongodb_version use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key use_same_kms_key_for_backups = var.use_same_kms_key_for_backups @@ -15,7 +16,6 @@ module "mongodb" { cbr_rules = var.cbr_rules access_tags = var.access_tags tags = var.tags - plan = var.plan members = var.members memory_mb = var.memory_mb admin_pass = var.admin_pass diff --git a/modules/fscloud/variables.tf b/modules/fscloud/variables.tf index 34999377..5b74eb7c 100644 --- a/modules/fscloud/variables.tf +++ b/modules/fscloud/variables.tf @@ -7,7 +7,7 @@ variable "resource_group_id" { description = "The resource group ID where the MongoDB instance will be created." } -variable "instance_name" { +variable "name" { type = string description = "Name of the mongodb instance" } @@ -202,8 +202,17 @@ variable "cbr_rules" { value = string }))) })) enforcement_mode = string + tags = optional(list(object({ + name = string + value = string + }))) + operations = optional(list(object({ + api_types = list(object({ + api_type_id = string + })) + }))) })) - description = "(Optional, list) List of CBR rules to create" + description = "(Optional, list) List of context-based restrictions rules to create." default = [] # Validation happens in the rule module } diff --git a/solutions/standard/DA-types.md b/solutions/standard/DA-types.md index 8a8bc45c..082022f8 100644 --- a/solutions/standard/DA-types.md +++ b/solutions/standard/DA-types.md @@ -197,3 +197,45 @@ The following example shows values for both disk and memory for the `auto_scalin } } ``` + +## Configuration + +The Configuration variable tunes the Redis database to suit different use case. For more information, see [Configuration](https://cloud.ibm.com/docs/databases-for-redis?topic=databases-for-redis-changing-configuration&interface=cli). + +- Variable name: `configuration` +- Type: An object with `maxmemory`, `maxmemory-policy`, `appendonly`, `maxmemory-samples` and `stop-writes-on-bgsave-error` attributes +- Default value: An object with following configuration: +``` +{ + maxmemory : 80, + maxmemory-policy : "noeviction", + appendonly : "yes", + maxmemory-samples : 5, + stop-writes-on-bgsave-error : "yes" +} +``` + +### Options for configuration + +The configuration object in the input contains the following options. All options are optional. + +- `maxmemory`: Determines the amount of data that you can store in Redis as a percentage of the deployments memory. (default: `80`). +- `maxmemory-policy`: Determines eviction behavior when `maxmemory` limit is reached [Learn more](https://cloud.ibm.com/docs/databases-for-redis?topic=databases-for-redis-redis-cache&interface=cli#redis-cache-maxmemory-policy) (default: `noeviction`). +- `appendonly`: Enables Redis persistence when set to `yes`, If you are caching data, you want to set this value to `no`. (default: `yes`). +- `maxmemory-samples`: Tunes LRU eviction algorithm when Redis is configured as a cache [Learn more](https://cloud.ibm.com/docs/databases-for-redis?topic=databases-for-redis-redis-cache&interface=cli#redis-cache-other-settings) (default: `5`). +- `stop-writes-on-bgsave-error`: Redis stops accepting writes if it detects an unsuccessful backup snapshot. For caching, you can set to `no`. (default: `yes`). + + +### Example configuration + +The following example shows values for the `configuration` input. + +```hcl +{ + "maxmemory": 80, + "maxmemory-policy": "noeviction", + "appendonly": "yes", + "maxmemory-samples": 5, + "stop-writes-on-bgsave-error": "yes" +} +``` diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf index a4c7c766..3d4c337a 100644 --- a/solutions/standard/main.tf +++ b/solutions/standard/main.tf @@ -5,7 +5,7 @@ module "resource_group" { source = "terraform-ibm-modules/resource-group/ibm" version = "1.2.0" - resource_group_name = var.use_existing_resource_group == false ? (var.prefix != null ? "${var.prefix}-${var.resource_group_name}" : var.resource_group_name) : null + resource_group_name = var.use_existing_resource_group == false ? ((var.prefix != null && var.prefix != "") ? "${var.prefix}-${var.resource_group_name}" : var.resource_group_name) : null existing_resource_group_name = var.use_existing_resource_group == true ? var.resource_group_name : null } @@ -18,17 +18,18 @@ module "resource_group" { locals { # tflint-ignore: terraform_unused_declarations - validate_kms_1 = var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn != null || var.existing_kms_key_crn != null || var.existing_backup_kms_key_crn != null) ? tobool("When setting values for 'existing_kms_instance_crn', 'existing_kms_key_crn' or 'existing_backup_kms_key_crn', the 'use_ibm_owned_encryption_key' input must be set to false.") : true + validate_kms_1 = var.existing_mongodb_instance_crn != null ? true : var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn != null || var.existing_kms_key_crn != null || var.existing_backup_kms_key_crn != null) ? tobool("When setting values for 'existing_kms_instance_crn', 'existing_kms_key_crn' or 'existing_backup_kms_key_crn', the 'use_ibm_owned_encryption_key' input must be set to false.") : true # tflint-ignore: terraform_unused_declarations - validate_kms_2 = !var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null) ? tobool("When 'use_ibm_owned_encryption_key' is false, a value is required for either 'existing_kms_instance_crn' (to create a new key), or 'existing_kms_key_crn' to use an existing key.") : true + validate_kms_2 = var.existing_mongodb_instance_crn != null ? true : !var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null) ? tobool("When 'use_ibm_owned_encryption_key' is false, a value is required for either 'existing_kms_instance_crn' (to create a new key), or 'existing_kms_key_crn' to use an existing key.") : true } + ####################################################################################################################### # KMS encryption key ####################################################################################################################### locals { - create_new_kms_key = !var.use_ibm_owned_encryption_key && var.existing_kms_key_crn == null ? true : false # no need to create any KMS resources if passing an existing key, or using IBM owned keys + create_new_kms_key = var.existing_mongodb_instance_crn == null && !var.use_ibm_owned_encryption_key && var.existing_kms_key_crn == null ? 1 : 0 # no need to create any KMS resources if passing an existing key, or using IBM owned keys mongodb_key_name = var.prefix != null ? "${var.prefix}-${var.key_name}" : var.key_name mongodb_key_ring_name = var.prefix != null ? "${var.prefix}-${var.key_ring_name}" : var.key_ring_name } @@ -37,7 +38,7 @@ module "kms" { providers = { ibm = ibm.kms } - count = local.create_new_kms_key ? 1 : 0 + count = local.create_new_kms_key source = "terraform-ibm-modules/kms-all-inclusive/ibm" version = "4.21.6" create_key_protect_instance = false @@ -99,23 +100,25 @@ data "ibm_iam_account_settings" "iam_account_settings" { locals { account_id = data.ibm_iam_account_settings.iam_account_settings.account_id - create_cross_account_kms_auth_policy = !var.skip_mongodb_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key - create_cross_account_backup_kms_auth_policy = !var.skip_mongodb_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key && var.existing_backup_kms_key_crn != null + create_cross_account_kms_auth_policy = var.existing_mongodb_instance_crn == null && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key + create_cross_account_backup_kms_auth_policy = var.existing_mongodb_instance_crn == null && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key && var.existing_backup_kms_key_crn != null + # If KMS encryption enabled (and existing ES instance is not being passed), parse details from the existing key if being passed, otherwise get it from the key that the DA creates - kms_account_id = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].account_id : module.kms_instance_crn_parser[0].account_id - kms_service = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_name : module.kms_instance_crn_parser[0].service_name - kms_instance_guid = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_instance : module.kms_instance_crn_parser[0].service_instance - kms_key_crn = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.mongodb_key_ring_name, local.mongodb_key_name)].crn - kms_key_id = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].resource : module.kms[0].keys[format("%s.%s", local.mongodb_key_ring_name, local.mongodb_key_name)].key_id - kms_region = var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].region : module.kms_instance_crn_parser[0].region + kms_account_id = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].account_id : module.kms_instance_crn_parser[0].account_id + kms_service = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_name : module.kms_instance_crn_parser[0].service_name + kms_instance_guid = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].service_instance : module.kms_instance_crn_parser[0].service_instance + kms_key_crn = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.mongodb_key_ring_name, local.mongodb_key_name)].crn + kms_key_id = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].resource : module.kms[0].keys[format("%s.%s", local.mongodb_key_ring_name, local.mongodb_key_name)].key_id + kms_region = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].region : module.kms_instance_crn_parser[0].region + # If creating KMS cross account policy for backups, parse backup key details from passed in key CRN backup_kms_account_id = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].account_id : local.kms_account_id backup_kms_service = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].service_name : local.kms_service backup_kms_instance_guid = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].service_instance : local.kms_instance_guid backup_kms_key_id = local.create_cross_account_backup_kms_auth_policy ? module.kms_backup_key_crn_parser[0].resource : local.kms_key_id - backup_kms_key_crn = var.use_ibm_owned_encryption_key ? null : var.existing_backup_kms_key_crn + backup_kms_key_crn = var.existing_mongodb_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_backup_kms_key_crn # Always use same key for backups unless user explicially passed a value for 'existing_backup_kms_key_crn' use_same_kms_key_for_backups = var.existing_backup_kms_key_crn == null ? true : false } @@ -233,19 +236,66 @@ locals { # if - replace first char with J # elseif _ replace first char with K # else use asis - admin_pass = var.admin_pass == null ? (startswith(random_password.admin_password[0].result, "-") ? "J${substr(random_password.admin_password[0].result, 1, -1)}" : startswith(random_password.admin_password[0].result, "_") ? "K${substr(random_password.admin_password[0].result, 1, -1)}" : random_password.admin_password[0].result) : var.admin_pass + generated_admin_password = startswith(random_password.admin_password[0].result, "-") ? "J${substr(random_password.admin_password[0].result, 1, -1)}" : startswith(random_password.admin_password[0].result, "_") ? "K${substr(random_password.admin_password[0].result, 1, -1)}" : random_password.admin_password[0].result + + + # admin password to use + admin_pass = var.admin_pass == null ? local.generated_admin_password : var.admin_pass } ####################################################################################################################### # MongoDB ####################################################################################################################### +# Look up existing instance details if user passes one +module "mongodb_instance_crn_parser" { + count = var.existing_mongodb_instance_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_mongodb_instance_crn +} + +# Existing instance local vars +locals { + existing_mongodb_guid = var.existing_mongodb_instance_crn != null ? module.mongodb_instance_crn_parser[0].service_instance : null + existing_mongodb_region = var.existing_mongodb_instance_crn != null ? module.mongodb_instance_crn_parser[0].region : null + + # Validate the region input matches region detected in existing instance CRN (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) + # tflint-ignore: terraform_unused_declarations + validate_existing_instance_region = var.existing_mongodb_instance_crn != null && var.region != local.existing_mongodb_region ? tobool("The region detected in the 'existing_mongodb_instance_crn' value must match the value of the 'region' input variable when passing an existing instance.") : true +} + +# Do a data lookup on the resource GUID to get more info that is needed for the 'ibm_database' data lookup below +data "ibm_resource_instance" "existing_instance_resource" { + count = var.existing_mongodb_instance_crn != null ? 1 : 0 + identifier = local.existing_mongodb_guid +} + +# Lookup details of existing instance +data "ibm_database" "existing_db_instance" { + count = var.existing_mongodb_instance_crn != null ? 1 : 0 + name = data.ibm_resource_instance.existing_instance_resource[0].name + resource_group_id = data.ibm_resource_instance.existing_instance_resource[0].resource_group_id + location = var.region + service = "databases-for-mongodb" +} + +# Lookup existing instance connection details +data "ibm_database_connection" "existing_connection" { + count = var.existing_mongodb_instance_crn != null ? 1 : 0 + endpoint_type = "private" + deployment_id = data.ibm_database.existing_db_instance[0].id + user_id = data.ibm_database.existing_db_instance[0].adminuser + user_type = "database" +} + # Create new instance module "mongodb" { + count = var.existing_mongodb_instance_crn != null ? 0 : 1 source = "../../modules/fscloud" depends_on = [time_sleep.wait_for_authorization_policy, time_sleep.wait_for_backup_kms_authorization_policy] resource_group_id = module.resource_group.resource_group_id - instance_name = var.prefix != null ? "${var.prefix}-${var.name}" : var.name + name = (var.prefix != null && var.prefix != "") ? "${var.prefix}-${var.name}" : var.name plan = var.plan region = var.region mongodb_version = var.mongodb_version @@ -261,32 +311,63 @@ module "mongodb" { users = var.users members = var.members member_host_flavor = var.member_host_flavor - memory_mb = var.member_memory_mb - disk_mb = var.member_disk_mb - cpu_count = var.member_cpu_count + memory_mb = var.memory_mb + disk_mb = var.disk_mb + cpu_count = var.cpu_count auto_scaling = var.auto_scaling service_credential_names = var.service_credential_names backup_crn = var.backup_crn } locals { - create_sm_auth_policy = var.skip_mongodb_sm_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 + mongodb_guid = var.existing_mongodb_instance_crn != null ? data.ibm_database.existing_db_instance[0].guid : module.mongodb[0].guid + mongodb_id = var.existing_mongodb_instance_crn != null ? data.ibm_database.existing_db_instance[0].id : module.mongodb[0].id + mongodb_version = var.existing_mongodb_instance_crn != null ? data.ibm_database.existing_db_instance[0].version : module.mongodb[0].version + mongodb_crn = var.existing_mongodb_instance_crn != null ? var.existing_mongodb_instance_crn : module.mongodb[0].crn + mongodb_hostname = var.existing_mongodb_instance_crn != null ? data.ibm_database_connection.existing_connection[0].mongodb[0].hosts[0].hostname : module.mongodb[0].hostname + mongodb_port = var.existing_mongodb_instance_crn != null ? data.ibm_database_connection.existing_connection[0].mongodb[0].hosts[0].port : module.mongodb[0].port +} + +####################################################################################################################### +# Secrets management +####################################################################################################################### + +locals { + ## Variable validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) + # tflint-ignore: terraform_unused_declarations + validate_secret_manager_crn = length(local.service_credential_secrets) > 0 && var.existing_secrets_manager_instance_crn == null ? tobool("`existing_secrets_manager_instance_crn` is required when adding service credentials to a secrets manager secret.") : false + # tflint-ignore: terraform_unused_declarations + validate_secret_manager_sg = var.existing_secrets_manager_instance_crn != null && var.admin_pass_secret_manager_secret_group == null ? tobool("`admin_pass_secret_manager_secret_group` is required when `existing_secrets_manager_instance_crn` is set.") : false + # tflint-ignore: terraform_unused_declarations + validate_secret_manager_sn = var.existing_secrets_manager_instance_crn != null && var.admin_pass_secret_manager_secret_name == null ? tobool("`admin_pass_secret_manager_secret_name` is required when `existing_secrets_manager_instance_crn` is set.") : false + + create_secret_manager_auth_policy = var.skip_mongodb_secret_manager_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 } +# Parse the Secrets Manager CRN +module "secret_manager_instance_crn_parser" { + count = var.existing_secrets_manager_instance_crn != null ? 1 : 0 + source = "terraform-ibm-modules/common-utilities/ibm//modules/crn-parser" + version = "1.1.0" + crn = var.existing_secrets_manager_instance_crn +} + + # create a service authorization between Secrets Manager and the target service (Databases for MongoDB) resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { - count = local.create_sm_auth_policy + count = local.create_secret_manager_auth_policy + depends_on = [module.mongodb] source_service_name = "secrets-manager" source_resource_instance_id = local.existing_secrets_manager_instance_guid target_service_name = "databases-for-mongodb" - target_resource_instance_id = module.mongodb.guid + target_resource_instance_id = local.mongodb_guid roles = ["Key Manager"] description = "Allow Secrets Manager with instance id ${local.existing_secrets_manager_instance_guid} to manage key for the databases-for-mongodb instance" } # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 resource "time_sleep" "wait_for_mongodb_authorization_policy" { - count = local.create_sm_auth_policy + count = local.create_secret_manager_auth_policy depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager] create_duration = "30s" } @@ -307,19 +388,30 @@ locals { service_credentials_ttl = secret.service_credentials_ttl service_credential_secret_description = secret.service_credential_secret_description service_credentials_source_service_role_crn = secret.service_credentials_source_service_role_crn - service_credentials_source_service_crn = module.mongodb.crn + service_credentials_source_service_crn = local.mongodb_crn secret_type = "service_credentials" #checkov:skip=CKV_SECRET_6 } ] } ] - existing_secrets_manager_instance_crn_split = var.existing_secrets_manager_instance_crn != null ? split(":", var.existing_secrets_manager_instance_crn) : null - existing_secrets_manager_instance_guid = var.existing_secrets_manager_instance_crn != null ? element(local.existing_secrets_manager_instance_crn_split, length(local.existing_secrets_manager_instance_crn_split) - 3) : null - existing_secrets_manager_instance_region = var.existing_secrets_manager_instance_crn != null ? element(local.existing_secrets_manager_instance_crn_split, length(local.existing_secrets_manager_instance_crn_split) - 5) : null + # Build the structure of the arbitrary credential type secret for admin password + admin_pass_secret = [{ + secret_group_name = (var.prefix != null && var.prefix != "") && var.admin_pass_secret_manager_secret_group != null ? "${var.prefix}-${var.admin_pass_secret_manager_secret_group}" : var.admin_pass_secret_manager_secret_group + existing_secret_group = var.use_existing_admin_pass_secret_manager_secret_group + secrets = [{ + secret_name = (var.prefix != null && var.prefix != "") && var.admin_pass_secret_manager_secret_name != null ? "${var.prefix}-${var.admin_pass_secret_manager_secret_name}" : var.admin_pass_secret_manager_secret_name + secret_type = "arbitrary" + secret_payload_password = local.admin_pass + } + ] + }] - # tflint-ignore: terraform_unused_declarations - validate_sm_crn = length(local.service_credential_secrets) > 0 && var.existing_secrets_manager_instance_crn == null ? tobool("`existing_secrets_manager_instance_crn` is required when adding service credentials to a secrets manager secret.") : false + # Concatinate into 1 secrets object + secrets = concat(local.service_credential_secrets, local.admin_pass_secret) + # Parse Secrets Manager details from the CRN + existing_secrets_manager_instance_guid = var.existing_secrets_manager_instance_crn != null ? module.secret_manager_instance_crn_parser[0].service_instance : null + existing_secrets_manager_instance_region = var.existing_secrets_manager_instance_crn != null ? module.secret_manager_instance_crn_parser[0].region : null } module "secrets_manager_service_credentials" { @@ -330,5 +422,5 @@ module "secrets_manager_service_credentials" { existing_sm_instance_guid = local.existing_secrets_manager_instance_guid existing_sm_instance_region = local.existing_secrets_manager_instance_region endpoint_type = var.existing_secrets_manager_endpoint_type - secrets = local.service_credential_secrets + secrets = local.secrets } diff --git a/solutions/standard/moved.tf b/solutions/standard/moved.tf new file mode 100644 index 00000000..248dfb7d --- /dev/null +++ b/solutions/standard/moved.tf @@ -0,0 +1,4 @@ +moved { + from = module.mongodb + to = module.mongodb[0] +} diff --git a/solutions/standard/outputs.tf b/solutions/standard/outputs.tf index ae2e4141..89fb22bd 100644 --- a/solutions/standard/outputs.tf +++ b/solutions/standard/outputs.tf @@ -4,60 +4,44 @@ output "id" { description = "MongoDB instance id" - value = module.mongodb.id + value = local.mongodb_id } output "version" { description = "MongoDB instance version" - value = module.mongodb.version + value = local.mongodb_version } output "guid" { description = "MongoDB instance guid" - value = module.mongodb.guid + value = local.mongodb_guid } output "crn" { description = "MongoDB instance crn" - value = module.mongodb.crn -} - -output "cbr_rule_ids" { - description = "CBR rule ids created to restrict MongoDB" - value = module.mongodb.cbr_rule_ids + value = local.mongodb_crn } output "service_credentials_json" { description = "Service credentials json map" - value = module.mongodb.service_credentials_json + value = var.existing_mongodb_instance_crn != null ? null : module.mongodb[0].service_credentials_json sensitive = true } output "service_credentials_object" { description = "Service credentials object" - value = module.mongodb.service_credentials_object + value = var.existing_mongodb_instance_crn != null ? null : module.mongodb[0].service_credentials_object sensitive = true } -output "adminuser" { - description = "Database admin user name" - value = module.mongodb.adminuser -} - output "hostname" { description = "Database connection hostname" - value = module.mongodb.hostname + value = local.mongodb_hostname } output "port" { description = "Database connection port" - value = module.mongodb.port -} - -output "certificate_base64" { - description = "Database connection certificate" - value = module.mongodb.certificate_base64 - sensitive = true + value = local.mongodb_port } output "secrets_manager_secrets" { diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf index f87b2c1f..f471d1b2 100644 --- a/solutions/standard/variables.tf +++ b/solutions/standard/variables.tf @@ -56,6 +56,12 @@ variable "plan" { } } +variable "existing_mongodb_instance_crn" { + type = string + default = null + description = "The CRN of an existing Databases for MongoDB instance. If no value is specified, a new instance is created." +} + ############################################################################## # ICD hosting model properties ############################################################################## @@ -66,19 +72,19 @@ variable "members" { default = 3 } -variable "member_memory_mb" { +variable "memory_mb" { type = number description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-resources-scaling)" default = 4096 } -variable "member_cpu_count" { +variable "cpu_count" { type = number description = "The dedicated CPU per member that is allocated. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-resources-scaling)." default = 0 } -variable "member_disk_mb" { +variable "disk_mb" { type = number description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-resources-scaling)." default = 10240 @@ -88,6 +94,11 @@ variable "member_host_flavor" { type = string description = "The host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." default = "multitenant" + # Prevent null or "", require multitenant or a machine type + validation { + condition = (length(var.member_host_flavor) > 0) + error_message = "Member host flavor must be specified. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." + } } variable "service_credential_names" { @@ -303,8 +314,26 @@ variable "service_credential_secrets" { } } -variable "skip_mongodb_sm_auth_policy" { +variable "skip_mongodb_secret_manager_auth_policy" { type = bool description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Databases for MongoDB. If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the MongoDB instance created by the Deployable Architecture. Set to `true` to use an existing policy. The value of this is ignored if any value for 'existing_secrets_manager_instance_crn' is not passed." default = false } + +variable "admin_pass_secret_manager_secret_group" { + type = string + description = "The name of a new or existing secrets manager secret group for admin password. To use existing secret group, `use_existing_admin_pass_secrets_manager_secret_group` must be set to `true`. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "mongodb-secrets" +} + +variable "use_existing_admin_pass_secret_manager_secret_group" { + type = bool + description = "Whether to use an existing secrets manager secret group for admin password." + default = false +} + +variable "admin_pass_secret_manager_secret_name" { + type = string + description = "The name of a new mongodb administrator secret. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "mongodb-admin-password" +} diff --git a/tests/other_test.go b/tests/other_test.go index 79d298b0..47868c1d 100644 --- a/tests/other_test.go +++ b/tests/other_test.go @@ -3,30 +3,13 @@ package test import ( "fmt" + "testing" - "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" ) -func TestRunBasicExample(t *testing.T) { - t.Parallel() - - options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ - Testing: t, - TerraformDir: "examples/basic", - Prefix: "mongodb-basic", - BestRegionYAMLPath: regionSelectionPath, - ResourceGroup: resourceGroup, - CloudInfoService: sharedInfoSvc, - }) - - output, err := options.RunTestConsistency() - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") -} - func testPlanICDVersions(t *testing.T, version string) { t.Parallel() @@ -63,7 +46,7 @@ func TestRunRestoredDBExample(t *testing.T) { Region: fmt.Sprint(permanentResources["mongodbRegion"]), ResourceGroup: resourceGroup, TerraformVars: map[string]interface{}{ - "mongo_db_crn": permanentResources["mongodbCrn"], + "existing_database_crn": permanentResources["mongodbCrn"], }, CloudInfoService: sharedInfoSvc, }) @@ -72,37 +55,3 @@ func TestRunRestoredDBExample(t *testing.T) { assert.Nil(t, err, "This should not have errored") assert.NotNil(t, output, "Expected some output") } - -func TestRunFSCloudExample(t *testing.T) { - t.Parallel() - options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ - Testing: t, - TerraformDir: "examples/fscloud", - Prefix: "mongodb-fscloud", - Region: "us-south", // For FSCloud locking into us-south since that is where the HPCS permanent instance is - /* - Comment out the 'ResourceGroup' input to force this test to create a unique resource group to ensure tests do - not clash. This is due to the fact that an auth policy may already exist in this resource group since we are - re-using a permanent HPCS instance. By using a new resource group, the auth policy will not already exist - since this module scopes auth policies by resource group. - */ - //ResourceGroup: resourceGroup, - TerraformVars: map[string]interface{}{ - "access_tags": permanentResources["accessTags"], - "kms_key_crn": permanentResources["hpcs_south_root_key_crn"], - "mongodb_version": "7.0", // Always lock this test into the latest supported MongoDB version - }, - CloudInfoService: sharedInfoSvc, - }) - options.SkipTestTearDown = true - output, err := options.RunTestConsistency() - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") - - // check if outputs exist - outputs := terraform.OutputAll(options.Testing, options.TerraformOptions) - expectedOutputs := []string{"port", "hostname"} - _, outputErr := testhelper.ValidateTerraformOutputs(outputs, expectedOutputs...) - assert.NoErrorf(t, outputErr, "Some outputs not found or nil") - options.TestTearDown() -} diff --git a/tests/pr_test.go b/tests/pr_test.go index 7bea69ee..f793b26d 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -2,18 +2,16 @@ package test import ( - "crypto/rand" - "encoding/base64" - "encoding/json" "fmt" - "io/fs" "log" + "math/rand" "os" - "path/filepath" "strings" "testing" + "github.com/gruntwork-io/terratest/modules/files" "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,7 +21,9 @@ import ( "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) +const fscloudExampleTerraformDir = "examples/fscloud" const standardSolutionTerraformDir = "solutions/standard" +const latestVersion = "7.0" // Use existing resource group const resourceGroup = "geretain-test-mongo" @@ -37,6 +37,10 @@ const yamlLocation = "../common-dev-assets/common-go-assets/common-permanent-res var permanentResources map[string]interface{} var sharedInfoSvc *cloudinfo.CloudInfoService +var validICDRegions = []string{ + "eu-de", + "us-south", +} // TestMain will be run before any parallel tests, used to read data from yaml for use with tests func TestMain(m *testing.M) { @@ -51,80 +55,21 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -type tarIncludePatterns struct { - excludeDirs []string - - includeFiletypes []string - - includeDirs []string -} - -func getTarIncludePatternsRecursively(dir string, dirsToExclude []string, fileTypesToInclude []string) ([]string, error) { - r := tarIncludePatterns{dirsToExclude, fileTypesToInclude, nil} - err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { - return walk(&r, path, entry, err) - }) - if err != nil { - fmt.Println("error") - return r.includeDirs, err - } - return r.includeDirs, nil -} - -func walk(r *tarIncludePatterns, s string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - for _, excludeDir := range r.excludeDirs { - if strings.Contains(s, excludeDir) { - return nil - } - } - if s == ".." { - r.includeDirs = append(r.includeDirs, "*.tf") - return nil - } - for _, includeFiletype := range r.includeFiletypes { - r.includeDirs = append(r.includeDirs, strings.ReplaceAll(s+"/*"+includeFiletype, "../", "")) - } - } - return nil -} - func TestRunStandardSolutionSchematics(t *testing.T) { t.Parallel() - excludeDirs := []string{ - ".terraform", - ".docs", - ".github", - ".git", - ".idea", - "common-dev-assets", - "examples", - "tests", - "reference-architectures", - } - includeFiletypes := []string{ - ".tf", - ".yaml", - ".py", - ".tpl", - ".sh", - } - - tarIncludePatterns, recurseErr := getTarIncludePatternsRecursively("..", excludeDirs, includeFiletypes) - - // if error producing tar patterns (very unexpected) fail test immediately - require.NoError(t, recurseErr, "Schematic Test had unexpected error traversing directory tree") - prefix := "mongodb-st-da" options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ - Testing: t, - TarIncludePatterns: tarIncludePatterns, + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", standardSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), + fmt.Sprintf("%s/*.tf", "modules/fscloud"), + fmt.Sprintf("%s/*.sh", "scripts"), + }, TemplateFolder: standardSolutionTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: prefix, + Prefix: "mdb-st-da", ResourceGroup: resourceGroup, DeleteWorkspaceOnFail: false, WaitJobCompleteMinutes: 60, @@ -146,42 +91,36 @@ func TestRunStandardSolutionSchematics(t *testing.T) { }, } - serviceCredentialNames := map[string]string{ - "admin": "Administrator", - "user1": "Viewer", - "user2": "Editor", - } - - serviceCredentialNamesJSON, err := json.Marshal(serviceCredentialNames) - if err != nil { - log.Fatalf("Error converting to JSON: %s", err) - } - options.TerraformVars = []testschematic.TestSchematicTerraformVar{ {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, + {Name: "existing_backup_kms_key_crn", Value: permanentResources["hpcs_south_root_key_crn"], DataType: "string"}, {Name: "kms_endpoint_type", Value: "private", DataType: "string"}, {Name: "mongodb_version", Value: "7.0", DataType: "string"}, // Always lock this test into the latest supported MongoDB version {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, + {Name: "plan", Value: "standard", DataType: "string"}, + {Name: "service_credential_names", Value: "{\"admin_test\": \"Administrator\", \"editor_test\": \"Editor\"}", DataType: "map(string)"}, {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, - {Name: "service_credential_names", Value: string(serviceCredentialNamesJSON), DataType: "map(string)"}, - {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + {Name: "admin_pass_secret_manager_secret_group", Value: options.Prefix, DataType: "string"}, + {Name: "admin_pass_secret_manager_secret_name", Value: options.Prefix, DataType: "string"}, + {Name: "provider_visibility", Value: "private", DataType: "string"}, + {Name: "prefix", Value: options.Prefix, DataType: "string"}, } - err = options.RunSchematicTest() + err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") } - func TestRunStandardUpgradeSolution(t *testing.T) { t.Parallel() options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ - Testing: t, - TerraformDir: standardSolutionTerraformDir, - BestRegionYAMLPath: regionSelectionPath, - Prefix: "mongodb-st-da-upg", - ResourceGroup: resourceGroup, + Testing: t, + TerraformDir: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: "mongodb-st-da-upg", + ResourceGroup: resourceGroup, + CheckApplyResultForUpgrade: true, }) options.TerraformVars = map[string]interface{}{ @@ -199,106 +138,101 @@ func TestRunStandardUpgradeSolution(t *testing.T) { } } -func TestRunCompleteUpgradeExample(t *testing.T) { +func TestRunExistingInstance(t *testing.T) { t.Parallel() - - // Generate a 15 char long random string for the admin_pass - randomBytes := make([]byte, 13) - _, err := rand.Read(randomBytes) - if err != nil { - log.Fatal(err) - } - randomPass := "A1" + base64.URLEncoding.EncodeToString(randomBytes)[:13] - - options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ - Testing: t, - TerraformDir: "examples/complete", - Prefix: "mongodb-upg", - BestRegionYAMLPath: regionSelectionPath, - ResourceGroup: resourceGroup, - TerraformVars: map[string]interface{}{ - "mongodb_version": "6.0", // Always lock to the lowest supported MongoDB version - "users": []map[string]interface{}{ - { - "name": "testuser", - "password": randomPass, // pragma: allowlist secret - "type": "database", - }, - }, - "admin_pass": randomPass, + prefix := fmt.Sprintf("mongodb-t-%s", strings.ToLower(random.UniqueId())) + realTerraformDir := ".." + tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) + region := validICDRegions[rand.Intn(len(validICDRegions))] + + // Verify ibmcloud_api_key variable is set + checkVariable := "TF_VAR_ibmcloud_api_key" + val, present := os.LookupEnv(checkVariable) + require.True(t, present, checkVariable+" environment variable not set") + require.NotEqual(t, "", val, checkVariable+" environment variable is empty") + + logger.Log(t, "Tempdir: ", tempTerraformDir) + existingTerraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: tempTerraformDir + "/examples/basic", + Vars: map[string]interface{}{ + "prefix": prefix, + "region": region, + "mongodb_version": latestVersion, + "service_endpoints": "private", }, - CloudInfoService: sharedInfoSvc, + // Set Upgrade to true to ensure latest version of providers and modules are used by terratest. + // This is the same as setting the -upgrade=true flag with terraform. + Upgrade: true, }) - output, err := options.RunTestUpgrade() - if !options.UpgradeTestSkipped { + terraform.WorkspaceSelectOrNew(t, existingTerraformOptions, prefix) + _, existErr := terraform.InitAndApplyE(t, existingTerraformOptions) + if existErr != nil { + assert.True(t, existErr == nil, "Init and Apply of temp existing resource failed") + } else { + logger.Log(t, "existing_mongodb_instance_crn: ", terraform.Output(t, existingTerraformOptions, "mongodb_crn")) + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", standardSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), + fmt.Sprintf("%s/*.tf", "modules/fscloud"), + fmt.Sprintf("%s/*.sh", "scripts"), + }, + TemplateFolder: standardSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: "mongodb-sr-da", + ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, + }) + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "existing_mongodb_instance_crn", Value: terraform.Output(t, existingTerraformOptions, "mongodb_crn"), DataType: "string"}, + {Name: "resource_group_name", Value: fmt.Sprintf("%s-resource-group", prefix), DataType: "string"}, + {Name: "region", Value: region, DataType: "string"}, + {Name: "use_existing_resource_group", Value: true, DataType: "bool"}, + {Name: "provider_visibility", Value: "public", DataType: "string"}, + } + err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") + + } + envVal, _ := os.LookupEnv("DO_NOT_DESTROY_ON_FAILURE") + // Destroy the temporary existing resources if required + if t.Failed() && strings.ToLower(envVal) == "true" { + fmt.Println("Terratest failed. Debug the test and delete resources manually.") + } else { + logger.Log(t, "START: Destroy (existing resources)") + terraform.Destroy(t, existingTerraformOptions) + terraform.WorkspaceDelete(t, existingTerraformOptions, prefix) + logger.Log(t, "END: Destroy (existing resources)") } + } -func TestPlanValidation(t *testing.T) { +// Test the DA when using IBM owned encryption keys +func TestRunStandardSolutionIBMKeys(t *testing.T) { + t.Parallel() + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ Testing: t, TerraformDir: standardSolutionTerraformDir, - Prefix: "validate-plan", + Region: "us-south", + Prefix: "mongodb-icd-key", ResourceGroup: resourceGroup, - Region: "us-south", // skip VPC region picker }) - options.TestSetup() - options.TerraformOptions.NoColor = true - options.TerraformOptions.Logger = logger.Discard - options.TerraformOptions.Vars = map[string]interface{}{ - "prefix": options.Prefix, - "region": "us-south", - "mongodb_version": "7.0", - "provider_visibility": "public", - "resource_group_name": options.Prefix, - } - - // Test the DA when using an existing KMS instance - var standardSolutionWithExistingKms = map[string]interface{}{ - "access_tags": permanentResources["accessTags"], - "existing_kms_instance_crn": permanentResources["hpcs_south_crn"], - } - // Test the DA when using IBM owned encryption key - var standardSolutionWithUseIbmOwnedEncKey = map[string]interface{}{ + options.TerraformVars = map[string]interface{}{ + "mongodb_version": "7.0", + "provider_visibility": "public", + "resource_group_name": options.Prefix, "use_ibm_owned_encryption_key": true, } - // Create a map of the variables - tfVarsMap := map[string]map[string]interface{}{ - "standardSolutionWithExistingKms": standardSolutionWithExistingKms, - "standardSolutionWithUseIbmOwnedEncKey": standardSolutionWithUseIbmOwnedEncKey, - } - - _, initErr := terraform.InitE(t, options.TerraformOptions) - if assert.Nil(t, initErr, "This should not have errored") { - // Iterate over the slice of maps - for name, tfVars := range tfVarsMap { - t.Run(name, func(t *testing.T) { - // Iterate over the keys and values in each map - for key, value := range tfVars { - options.TerraformOptions.Vars[key] = value - } - output, err := terraform.PlanE(t, options.TerraformOptions) - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") - // Delete the keys from the map - for key := range tfVars { - delete(options.TerraformOptions.Vars, key) - } - }) - } - } -} - -func GetRandomAdminPassword(t *testing.T) string { - // Generate a 15 char long random string for the admin_pass - randomBytes := make([]byte, 13) - _, randErr := rand.Read(randomBytes) - require.Nil(t, randErr) // do not proceed if we can't gen a random password - randomPass := "A1" + base64.URLEncoding.EncodeToString(randomBytes)[:13] - return randomPass + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") } diff --git a/variables.tf b/variables.tf index c043b88e..bc918c5e 100644 --- a/variables.tf +++ b/variables.tf @@ -7,7 +7,7 @@ variable "resource_group_id" { description = "The resource group ID where the MongoDB instance will be created." } -variable "instance_name" { +variable "name" { type = string description = "The name to give the MongoDB instance." } @@ -53,7 +53,7 @@ variable "plan" { variable "members" { type = number - description = "Allocated number of members" + description = "The number of members that are allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-resources-scaling)" default = 3 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } @@ -67,7 +67,7 @@ variable "cpu_count" { variable "disk_mb" { type = number - description = "Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member)" + description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mongodb?topic=databases-for-mongodb-pricing#mongodb-scale-member)" default = 10240 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } @@ -116,14 +116,14 @@ variable "service_credential_names" { } } -variable "endpoints" { +variable "service_endpoints" { type = string - description = "Specify whether you want to enable the public, private, or both service endpoints. Supported values are 'public', 'private', or 'public-and-private'." - default = "private" + description = "The type of endpoint of the database instance. Possible values: `public`, `private`, `public-and-private`." + default = "public" validation { - condition = can(regex("public|private|public-and-private", var.endpoints)) - error_message = "Valid values are (public, private, or public-and-private)." + condition = can(regex("public|public-and-private|private", var.service_endpoints)) + error_message = "Valid values for service_endpoints are 'public', 'public-and-private', and 'private'" } } @@ -146,6 +146,7 @@ variable "access_tags" { } } + ############################################################## # Auto Scaling ############################################################## @@ -249,8 +250,17 @@ variable "cbr_rules" { value = string }))) })) enforcement_mode = string + tags = optional(list(object({ + name = string + value = string + }))) + operations = optional(list(object({ + api_types = list(object({ + api_type_id = string + })) + }))) })) - description = "(Optional, list) List of CBR rules to create" + description = "(Optional, list) List of context-based restrictions rules to create." default = [] # Validation happens in the rule module }