diff --git a/.catalog-onboard-pipeline.yaml b/.catalog-onboard-pipeline.yaml index 246cef0c..84df8ec8 100644 --- a/.catalog-onboard-pipeline.yaml +++ b/.catalog-onboard-pipeline.yaml @@ -6,9 +6,17 @@ offerings: catalog_id: 7df1e4ca-d54c-4fd0-82ce-3d13247308cd offering_id: 4be16b65-3efc-457e-bc30-690e3ac7a6a1 variations: - - name: standard + - name: fully-configurable mark_ready: true install_type: fullstack scc: instance_id: 1c7d5f78-9262-44c3-b779-b28fe4d88c37 region: us-south + scope_resource_group_var_name: existing_resource_group_name + - name: security-enforced + mark_ready: true + install_type: fullstack + scc: + instance_id: 1c7d5f78-9262-44c3-b779-b28fe4d88c37 + region: us-south + scope_resource_group_var_name: existing_resource_group_name diff --git a/README.md b/README.md index a3bf9bf0..32a27b2b 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,8 @@ To attach access management tags to resources in this module, you need the follo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.9.0 | -| [ibm](#requirement\_ibm) | >= 1.79.2, < 2.0.0 | -| [time](#requirement\_time) | >= 0.9.1 | +| [ibm](#requirement\_ibm) | >= 1.79.2, <2.0.0 | +| [time](#requirement\_time) | >= 0.9.1, < 1.0.0 | ### Modules @@ -104,7 +104,7 @@ To attach access management tags to resources in this module, you need the follo | [ibm_iam_authorization_policy.backup_kms_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_iam_authorization_policy.kms_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_resource_key.service_credentials](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_key) | resource | -| [ibm_resource_tag.mysql_tag](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_tag) | resource | +| [ibm_resource_tag.access_tag](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_tag) | resource | | [time_sleep.wait_for_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | | [time_sleep.wait_for_backup_kms_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | | [ibm_database_connection.database_connection](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/database_connection) | data source | @@ -115,7 +115,7 @@ To attach access management tags to resources in this module, you need the follo |------|-------------|------|---------|:--------:| | [access\_tags](#input\_access\_tags) | A list of access tags to apply to the MySQL instance created by the module, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial for more details | `list(string)` | `[]` | no | | [admin\_pass](#input\_admin\_pass) | The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block. | `string` | `null` | no | -| [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://ibm.biz/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 | +| [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-mysql?topic=databases-for-mysql-autoscaling-mysql&interface=ui 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
tags = optional(list(object({
name = string
value = string
})))
}))
| `[]` | no | @@ -125,7 +125,7 @@ To attach access management tags to resources in this module, you need the follo | [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. Members can be scaled up but not down. | `number` | `3` | no | -| [memory\_mb](#input\_memory\_mb) | Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `4096` | no | +| [memory\_mb](#input\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `4096` | no | | [mysql\_version](#input\_mysql\_version) | Version of the MySQL instance to provision. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | | [name](#input\_name) | The name given to the MySQL instance. | `string` | n/a | yes | | [pitr\_id](#input\_pitr\_id) | (Optional) The ID of the source deployment MySQL instance that you want to recover back to. The MySQL instance is expected to be in an up and in running state. | `string` | `null` | no | @@ -135,7 +135,7 @@ To attach access management tags to resources in this module, you need the follo | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the MySQL 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) | 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 | -| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of an IAM authorization policy that permits all MySQL database instances in the resource group to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing\_kms\_instance\_guid variable. In addition, no policy is created if var.kms\_encryption\_enabled is set to false. | `bool` | `false` | 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 MySQL 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 MySQL instance. | `list(string)` | `[]` | 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 | | [use\_ibm\_owned\_encryption\_key](#input\_use\_ibm\_owned\_encryption\_key) | IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input. | `bool` | `true` | no | diff --git a/cra-config.yaml b/cra-config.yaml index d86b3245..7652285d 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -1,11 +1,12 @@ # More info about this file at https://github.com/terraform-ibm-modules/common-pipeline-assets/blob/main/.github/workflows/terraform-test-pipeline.md#cra-config-yaml version: "v1" CRA_TARGETS: - - CRA_TARGET: "solutions/standard" # Target directory for CRA scan. If not provided, the CRA Scan will not be run. + - CRA_TARGET: "solutions/fully-configurable" # Target directory for CRA scan. If not provided, the CRA Scan will not be run. CRA_IGNORE_RULES_FILE: "cra-tf-validate-ignore-rules.json" # CRA Ignore file to use. If not provided, it checks the repo root directory for `cra-tf-validate-ignore-rules.json` PROFILE_ID: "fe96bd4d-9b37-40f2-b39f-a62760e326a3" # SCC profile ID (currently set to 'IBM Cloud Framework for Financial Services' '1.7.0' profile). CRA_ENVIRONMENT_VARIABLES: # An optional map of environment variables for CRA, where the key is the variable name and value is the value. Useful for providing TF_VARs. TF_VAR_existing_kms_key_crn: "crn:v1:bluemix:public:hs-crypto:us-south:a/abac0df06b644a9cabc6e44f55b3880e:e6dce284-e80f-46e1-a3c1-830f7adff7a9:key:76170fae-4e0c-48c3-8ebe-326059ebb533" - TF_VAR_use_existing_resource_group: true - TF_VAR_resource_group_name: "geretain-test-mysql" + TF_VAR_existing_resource_group_name: "geretain-test-mysql" + TF_VAR_kms_encryption_enabled: true TF_VAR_provider_visibility: "public" + TF_VAR_prefix: "test" diff --git a/ibm_catalog.json b/ibm_catalog.json index fcbd9aab..b196e9b2 100644 --- a/ibm_catalog.json +++ b/ibm_catalog.json @@ -9,6 +9,7 @@ "target_terraform", "terraform", "data_management", + "database", "solution" ], "keywords": [ @@ -17,40 +18,40 @@ "infrastructure as code", "terraform", "solution", - "mysql standard", "cache", "in memory" ], "short_description": "Creates and configures an instance of IBM Cloud Databases for MySQL.", - "long_description": "This architecture supports creating and configuring an instance of Databases for MySQL with KMS encryption.", + "long_description": "This architecture supports creating and configuring an instance of [Databases for MySQL](https://www.ibm.com/products/databases-for-mysql), with optional KMS encryption.\n\nℹ️ This Terraform-based automation is part of a broader suite of IBM-maintained Infrastructure as Code (IaC) asset collection, each following the naming pattern \"Cloud automation for *servicename*\" and focusing on single IBM Cloud service. These single-service deployable architectures can be used on their own to streamline and automate service deployments through an [IaC approach](https://cloud.ibm.com/docs/secure-enterprise?topic=secure-enterprise-understanding-projects), or assembled together into a broader [automated IaC stack](https://cloud.ibm.com/docs/secure-enterprise?topic=secure-enterprise-config-stack) to automate the deployment of an end-to-end solution architecture.", "offering_docs_url": "https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/README.md", "offering_icon_url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-icd-mysql/main/images/mysql_icon.svg", "provider_name": "IBM", "support_details": "This product is in the community registry, as such support is handled through the originated repo. If you experience issues please open an issue in the repository [https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/issues](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/issues). Please note this product is not supported via the IBM Cloud Support Center.", "features": [ { - "title": "Creates an instance of Databases for MySQL", - "description": "Creates and configures an IBM Cloud Databases for MySQL instance." + "title": "KMS encryption", + "description": "Provides [KMS encryption](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-key-protect&interface=ui) for the data that you store in the database, enhancing data security." }, { - "title": "Supports KMS encryption", - "description": "Provides KMS encryption for the data that you store in the database." + "title": "Autoscaling", + "description": "Provides the [autoscaling](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-autoscaling-mysql&interface=ui) to allow the database to increase resources in response to usage." }, { - "title": "Supports autoscaling", - "description": "Provides the autoscaling to allow the database to increase resources in response to usage." + "title": "Access tags", + "description": "Attaches access tags to the MySQL instance." }, { - "title": "Supports backup restoration", - "description": "Provides database restoration using a backup created by a deployment with the same service ID." + "title": "Backup restoration", + "description": "Provides [database restoration](https://cloud.ibm.com/docs/cloud-databases?topic=cloud-databases-dashboard-backups&interface=ui) using a backup created by a deployment with the same service ID." } ], "flavors": [ { - "label": "Standard", - "name": "standard", + "label": "Fully configurable", + "name": "fully-configurable", + "index": 1, "install_type": "fullstack", - "working_directory": "solutions/standard", + "working_directory": "solutions/fully-configurable", "compliance": { "authority": "scc-v3", "profiles": [ @@ -63,9 +64,10 @@ "iam_permissions": [ { "role_crns": [ - "crn:v1:bluemix:public:iam::::role:Administrator" + "crn:v1:bluemix:public:iam::::role:Viewer" ], - "service_name": "all-account-management-services" + "service_name": "Resource group only", + "notes": "Viewer access is required in the resource group you want to provision in." }, { "role_crns": [ @@ -75,17 +77,24 @@ }, { "role_crns": [ - "crn:v1:bluemix:public:iam::::serviceRole:Manager", "crn:v1:bluemix:public:iam::::role:Editor" ], - "service_name": "kms" + "service_name": "kms", + "notes": "[Optional] Editor access is required to create keys. It is only required when using Key Protect for encryption." + }, + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Editor" + ], + "service_name": "hs-crypto", + "notes": "[Optional] Editor access is required to create keys in HPCS. It is only required when using HPCS for encryption." } ], "architecture": { "features": [ { - "title": " Creates an instance of Databases for MySQL", - "description": "This architecture creates an instance of IBM Cloud Databases for MySQL with KMS encryption. It accepts or creates a resource group, and provides autoscaling rules." + "title": " ", + "description": "Configured to use IBM secure by default standards, but can be edited to fit your use case." } ], "diagrams": [ @@ -105,6 +114,7 @@ }, { "key": "provider_visibility", + "hidden": true, "options": [ { "displayname": "private", @@ -121,21 +131,23 @@ ] }, { - "key": "use_existing_resource_group" - }, - { - "key": "resource_group_name" + "key": "existing_resource_group_name", + "display_name": "resource_group", + "custom_config": { + "type": "resource_group", + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "identifier": "rg_name" + } + } }, { "key": "prefix" }, - { - "key": "name" - }, { "key": "region", "required": true, - "default_value": "us-south", "options": [ { "displayname": "Chennai (che01)", @@ -205,6 +217,31 @@ } ] }, + { + "key": "name" + }, + { + "key": "resource_tags", + "type": "array", + "custom_config": { + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "type": "string" + } + } + }, + { + "key": "access_tags", + "type": "array", + "custom_config": { + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "type": "string" + } + } + }, { "key": "members" }, @@ -220,36 +257,97 @@ { "key": "member_host_flavor" }, + { + "key": "auto_scaling" + }, + { + "key": "configuration" + }, + { + "key": "service_endpoints", + "options": [ + { + "displayname": "private", + "value": "private" + }, + { + "displayname": "public", + "value": "public" + }, + { + "displayname": "public-and-private", + "value": "public-and-private" + } + ] + }, { "key": "service_credential_names" }, + { + "key": "service_credential_secrets", + "type": "array", + "custom_config": { + "type": "textarea", + "grouping": "deployment", + "original_grouping": "deployment" + } + }, { "key": "admin_pass" }, { - "key": "users" + "key": "existing_secrets_manager_instance_crn" + }, + { + "key": "existing_secrets_manager_endpoint_type", + "hidden": true, + "options": [ + { + "displayname": "public", + "value": "public" + }, + { + "displayname": "private", + "value": "private" + } + ] + }, + { + "key": "skip_mysql_secrets_manager_auth_policy" }, { - "key": "tags" + "key": "admin_pass_secrets_manager_secret_group" }, { - "key": "access_tags" + "key": "admin_pass_secrets_manager_secret_name" }, { - "key": "use_ibm_owned_encryption_key" + "key": "use_existing_admin_pass_secrets_manager_secret_group" + }, + { + "key": "users", + "type": "array", + "custom_config": { + "type": "textarea", + "grouping": "deployment", + "original_grouping": "deployment" + } }, { "key": "ibmcloud_kms_api_key" }, { - "key": "existing_kms_instance_crn", - "required": true + "key": "kms_encryption_enabled" + }, + { + "key": "existing_kms_instance_crn" }, { "key": "existing_kms_key_crn" }, { "key": "kms_endpoint_type", + "hidden": true, "options": [ { "displayname": "public", @@ -268,35 +366,244 @@ "key": "key_name" }, { - "key": "auto_scaling" + "key": "backup_crn" }, { - "key": "existing_secrets_manager_instance_crn" + "key": "existing_backup_kms_key_crn" }, { - "key": "existing_secrets_manager_endpoint_type", + "key": "use_default_backup_encryption_key" + }, + { + "key": "skip_mysql_kms_auth_policy" + }, + { + "key": "existing_mysql_instance_crn" + }, + { + "key": "remote_leader_crn" + } + ] + }, + { + "label": "Security-enforced", + "name": "security-enforced", + "index": 2, + "install_type": "fullstack", + "working_directory": "solutions/security-enforced", + "compliance": { + "authority": "scc-v3", + "profiles": [ + { + "profile_name": "IBM Cloud Framework for Financial Services", + "profile_version": "1.7.0" + } + ] + }, + "iam_permissions": [ + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Viewer" + ], + "service_name": "Resource group only", + "notes": "Viewer access is required in the resource group you want to provision in." + }, + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Editor" + ], + "service_name": "databases-for-mysql" + }, + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Editor" + ], + "service_name": "kms", + "notes": "[Optional] Editor access is required to create keys. It is required only if KMS encryption is enabled." + }, + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Editor" + ], + "service_name": "hs-crypto", + "notes": "[Optional] Editor access is required to create keys in HPCS. It is only required when using HPCS for encryption." + } + ], + "architecture": { + "features": [ + { + "title": " ", + "description": "Configured to use IBM secure by default standards that can't be changed." + } + ], + "diagrams": [ + { + "diagram": { + "caption": "Databases for MySQL instance on IBM Cloud", + "url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-icd-mysql/main/reference-architecture/deployable-architecture-mysql.svg", + "type": "image/svg+xml" + }, + "description": "This architecture supports creating and configuring an instance of Databases for MySQL instance with KMS encryption." + } + ] + }, + "configuration": [ + { + "key": "ibmcloud_api_key" + }, + { + "key": "existing_resource_group_name", + "display_name": "resource_group", + "custom_config": { + "type": "resource_group", + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "identifier": "rg_name" + } + } + }, + { + "key": "prefix" + }, + { + "key": "region", + "required": true, "options": [ { - "displayname": "public", - "value": "public" + "displayname": "Chennai (che01)", + "value": "che01" }, { - "displayname": "private", - "value": "private" + "displayname": "Dallas (us-south)", + "value": "us-south" + }, + { + "displayname": "Frankfurt (eu-de)", + "value": "eu-de" + }, + { + "displayname": "London (eu-gb)", + "value": "eu-gb" + }, + { + "displayname": "Madrid (eu-es)", + "value": "eu-es" + }, + { + "displayname": "Osaka (jp-osa)", + "value": "jp-osa" + }, + { + "displayname": "Paris (par01)", + "value": "par01" + }, + { + "displayname": "Sao Paulo (br-sao)", + "value": "br-sao" + }, + { + "displayname": "Sydney (au-syd)", + "value": "au-syd" + }, + { + "displayname": "Toronto (ca-tor)", + "value": "ca-tor" + }, + { + "displayname": "Tokyo (jp-tok)", + "value": "jp-tok" + }, + { + "displayname": "Washington (us-east)", + "value": "us-east" } ] }, { - "key": "service_credential_secrets" + "key": "mysql_version", + "required": true, + "options": [ + { + "displayname": "6.2", + "value": "6.2" + }, + { + "displayname": "7.2", + "value": "7.2" + }, + { + "displayname": "8.0", + "value": "8.0" + } + ] }, { - "key": "skip_mysql_secrets_manager_auth_policy" + "key": "name" }, { - "key": "skip_mysql_kms_auth_policy" + "key": "resource_tags", + "type": "array", + "custom_config": { + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "type": "string" + } + } }, { - "key": "backup_crn" + "key": "access_tags", + "type": "array", + "custom_config": { + "grouping": "deployment", + "original_grouping": "deployment", + "config_constraints": { + "type": "string" + } + } + }, + { + "key": "members" + }, + { + "key": "member_memory_mb" + }, + { + "key": "member_cpu_count" + }, + { + "key": "member_disk_mb" + }, + { + "key": "member_host_flavor" + }, + { + "key": "auto_scaling" + }, + { + "key": "configuration" + }, + { + "key": "service_credential_names" + }, + { + "key": "service_credential_secrets", + "type": "array", + "custom_config": { + "type": "textarea", + "grouping": "deployment", + "original_grouping": "deployment" + } + }, + { + "key": "admin_pass" + }, + { + "key": "existing_secrets_manager_instance_crn" + }, + { + "key": "skip_mysql_secrets_manager_auth_policy" }, { "key": "admin_pass_secrets_manager_secret_group" @@ -308,19 +615,44 @@ "key": "use_existing_admin_pass_secrets_manager_secret_group" }, { - "key": "existing_mysql_instance_crn" + "key": "users", + "type": "array", + "custom_config": { + "type": "textarea", + "grouping": "deployment", + "original_grouping": "deployment" + } + }, + { + "key": "ibmcloud_kms_api_key" + }, + { + "key": "existing_kms_instance_crn", + "required": true + }, + { + "key": "existing_kms_key_crn" + }, + { + "key": "key_ring_name" + }, + { + "key": "key_name" + }, + { + "key": "backup_crn" }, { "key": "existing_backup_kms_key_crn" }, { - "key": "use_default_backup_encryption_key" + "key": "skip_mysql_kms_auth_policy" }, { - "key": "remote_leader_crn" + "key": "existing_mysql_instance_crn" }, { - "key": "configuration" + "key": "remote_leader_crn" } ] } diff --git a/main.tf b/main.tf index 3f3ca6a7..052a24d6 100644 --- a/main.tf +++ b/main.tf @@ -1,8 +1,10 @@ -############################################################################## -# ICD MySQL module -############################################################################## +######################################################################################################################## +# Locals +######################################################################################################################## locals { + # 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. + # 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) @@ -159,24 +161,24 @@ resource "time_sleep" "wait_for_backup_kms_authorization_policy" { # MySQL instance ######################################################################################################################## -# Create MySQL database resource "ibm_database" "mysql_db" { - depends_on = [time_sleep.wait_for_authorization_policy] - resource_group_id = var.resource_group_id - name = var.name - service = "databases-for-mysql" - location = var.region - plan = "standard" # Only standard plan is available for mysql - backup_id = var.backup_crn - remote_leader_id = var.remote_leader_crn - version = var.mysql_version - tags = var.tags - adminpassword = var.admin_pass - service_endpoints = var.service_endpoints + depends_on = [time_sleep.wait_for_authorization_policy] + name = var.name + plan = "standard" # Only standard plan is available for mysql + location = var.region + service = "databases-for-mysql" + version = var.mysql_version + resource_group_id = var.resource_group_id + service_endpoints = var.service_endpoints + tags = var.tags + adminpassword = var.admin_pass + key_protect_key = var.kms_key_crn + backup_encryption_key_crn = local.backup_encryption_key_crn + backup_id = var.backup_crn + remote_leader_id = var.remote_leader_crn + # remove elements with null values: see https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/issues/273 configuration = var.configuration != null ? jsonencode({ for k, v in var.configuration : k => v if v != null }) : null - key_protect_key = var.kms_key_crn - backup_encryption_key_crn = local.backup_encryption_key_crn point_in_time_recovery_deployment_id = var.pitr_id point_in_time_recovery_time = var.pitr_time @@ -309,7 +311,7 @@ resource "ibm_database" "mysql_db" { } } -resource "ibm_resource_tag" "mysql_tag" { +resource "ibm_resource_tag" "access_tag" { count = length(var.access_tags) == 0 ? 0 : 1 resource_id = ibm_database.mysql_db.resource_crn tags = var.access_tags @@ -319,6 +321,7 @@ resource "ibm_resource_tag" "mysql_tag" { ############################################################################## # Context Based Restrictions ############################################################################## + module "cbr_rule" { count = length(var.cbr_rules) > 0 ? length(var.cbr_rules) : 0 source = "terraform-ibm-modules/cbr/ibm//modules/cbr-rule-module" @@ -345,6 +348,7 @@ module "cbr_rule" { } ] }] + # There is only 1 operation type for Redis so it is not exposed as a configuration operations = [{ api_types = [ { diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index 6f0019ad..a3c08687 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -14,13 +14,13 @@ The IBM Cloud Framework for Financial Services mandates the application of an in | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.9.0 | -| [ibm](#requirement\_ibm) | >= 1.79.2 | +| [ibm](#requirement\_ibm) | >=1.79.2, <2.0.0 | ### Modules | Name | Source | Version | |------|--------|---------| -| [mysql\_db](#module\_mysql\_db) | ../../ | n/a | +| [mysql\_db](#module\_mysql\_db) | ../.. | n/a | ### Resources @@ -32,18 +32,18 @@ No resources. |------|-------------|------|---------|:--------:| | [access\_tags](#input\_access\_tags) | A list of access tags to apply to the MySQL instance created by the module, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial for more details | `list(string)` | `[]` | no | | [admin\_pass](#input\_admin\_pass) | The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block. | `string` | `null` | no | -| [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://ibm.biz/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 | +| [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-mysql?topic=databases-for-mysql-autoscaling-mysql&interface=ui 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
tags = optional(list(object({
name = string
value = string
})))
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
}))
| `[]` | no | +| [cbr\_rules](#input\_cbr\_rules) | (Optional, list) List of CBR rules to create, if operations is not set it will default to api-type:data-plane |
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 | | [configuration](#input\_configuration) | Database configuration parameters, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-changing-configuration&interface=api for more details. |
object({
default_authentication_plugin = optional(string) # sha256_password,caching_sha2_password,mysql_native_password
innodb_buffer_pool_size_percentage = optional(number) # 10 ≤ value ≤ 100
innodb_flush_log_at_trx_commit = optional(number) # 0 ≤ value ≤ 2
innodb_log_buffer_size = optional(number) # 1048576 ≤ value ≤ 4294967295
innodb_log_file_size = optional(number) # 4194304 ≤ value ≤ 274877906900
innodb_lru_scan_depth = optional(number) # 128 ≤ value ≤ 2048
innodb_read_io_threads = optional(number) # 1 ≤ value ≤ 64
innodb_write_io_threads = optional(number) # 1 ≤ value ≤ 64
max_allowed_packet = optional(number) # 1024 ≤ value ≤ 1073741824
max_connections = optional(number) # 100 ≤ value ≤ 200000
max_prepared_stmt_count = optional(number) # 0 ≤ value ≤ 4194304
mysql_max_binlog_age_sec = optional(number) # 300 ≤ value ≤ 1073741823 Default: 1800
net_read_timeout = optional(number) # 1 ≤ value ≤ 7200
net_write_timeout = optional(number) # 1 ≤ value ≤ 7200
sql_mode = optional(string) # The comma-separated list of SQL modes applied on this server globally.
wait_timeout = optional(number) # 1 ≤ value ≤ 31536000
})
| `null` | 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-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `3` | no | -| [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `10240` | no | +| [cpu\_count](#input\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling | `number` | `3` | no | +| [disk\_mb](#input\_disk\_mb) | Allocated disk per member. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling | `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. Members can be scaled up but not down. | `number` | `3` | no | -| [memory\_mb](#input\_memory\_mb) | Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling) | `number` | `4096` | no | -| [mysql\_version](#input\_mysql\_version) | Version of the MySQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | +| [memory\_mb](#input\_memory\_mb) | Allocated memory per member. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling | `number` | `4096` | no | +| [mysql\_version](#input\_mysql\_version) | The version of MySQL. If null, the current default ICD MySQl version is used. | `string` | `null` | no | | [name](#input\_name) | The name to give the MySQL instance. | `string` | n/a | yes | | [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 | | [remote\_leader\_crn](#input\_remote\_leader\_crn) | A CRN of the leader database to make the replica(read-only) deployment. The leader database is created by a database deployment with the same service ID. A read-only replica is set up to replicate all of your data from the leader deployment to the replica deployment by using asynchronous replication. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-read-replicas | `string` | `null` | no | diff --git a/modules/fscloud/main.tf b/modules/fscloud/main.tf index 00d02057..df599d74 100644 --- a/modules/fscloud/main.tf +++ b/modules/fscloud/main.tf @@ -1,29 +1,29 @@ module "mysql_db" { - source = "../../" + source = "../.." resource_group_id = var.resource_group_id - name = var.name + mysql_version = var.mysql_version region = var.region - remote_leader_crn = var.remote_leader_crn skip_iam_authorization_policy = var.skip_iam_authorization_policy + name = var.name service_endpoints = "private" - mysql_version = var.mysql_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 - use_default_backup_encryption_key = var.use_default_backup_encryption_key - kms_key_crn = var.kms_key_crn - backup_crn = var.backup_crn - backup_encryption_key_crn = var.backup_encryption_key_crn - tags = var.tags - access_tags = var.access_tags cbr_rules = var.cbr_rules configuration = var.configuration - memory_mb = var.memory_mb - disk_mb = var.disk_mb cpu_count = var.cpu_count + memory_mb = var.memory_mb member_host_flavor = var.member_host_flavor members = var.members admin_pass = var.admin_pass users = var.users - service_credential_names = var.service_credential_names + disk_mb = var.disk_mb + use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key + use_same_kms_key_for_backups = var.use_same_kms_key_for_backups + use_default_backup_encryption_key = var.use_default_backup_encryption_key + kms_key_crn = var.kms_key_crn + backup_crn = var.backup_crn + backup_encryption_key_crn = var.backup_encryption_key_crn auto_scaling = var.auto_scaling + access_tags = var.access_tags + tags = var.tags + service_credential_names = var.service_credential_names + remote_leader_crn = var.remote_leader_crn } diff --git a/modules/fscloud/outputs.tf b/modules/fscloud/outputs.tf index af5fb9be..b0e1595f 100644 --- a/modules/fscloud/outputs.tf +++ b/modules/fscloud/outputs.tf @@ -7,21 +7,38 @@ output "id" { value = module.mysql_db.id } -output "guid" { - description = "MySQL instance guid" - value = module.mysql_db.guid -} - output "version" { description = "MySQL instance version" value = module.mysql_db.version } +output "guid" { + description = "MySQL instance guid" + value = module.mysql_db.guid +} + output "crn" { description = "MySQL instance crn" value = module.mysql_db.crn } +output "cbr_rule_ids" { + description = "CBR rule ids created to restrict MySQL" + value = module.mysql_db.cbr_rule_ids +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.mysql_db.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.mysql_db.service_credentials_object + sensitive = true +} + output "adminuser" { description = "Database admin user name" value = module.mysql_db.adminuser @@ -42,20 +59,3 @@ output "certificate_base64" { value = module.mysql_db.certificate_base64 sensitive = true } - -output "cbr_rule_ids" { - description = "CBR rule ids created to restrict MySQL" - value = module.mysql_db.cbr_rule_ids -} - -output "service_credentials_json" { - description = "Service credentials json map" - value = module.mysql_db.service_credentials_json - sensitive = true -} - -output "service_credentials_object" { - description = "Service credentials object" - value = module.mysql_db.service_credentials_object - sensitive = true -} diff --git a/modules/fscloud/variables.tf b/modules/fscloud/variables.tf index 4ad27571..570acc11 100644 --- a/modules/fscloud/variables.tf +++ b/modules/fscloud/variables.tf @@ -14,7 +14,7 @@ variable "name" { variable "mysql_version" { type = string - description = "Version of the MySQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used." + description = "The version of MySQL. If null, the current default ICD MySQl version is used." default = null } @@ -42,13 +42,13 @@ variable "members" { variable "cpu_count" { type = number - description = "Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" + description = "Allocated dedicated CPU per member. For shared CPU, set to 0. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling" default = 3 } variable "disk_mb" { type = number - description = "Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" + description = "Allocated disk per member. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling" default = 10240 } @@ -60,7 +60,7 @@ variable "member_host_flavor" { variable "memory_mb" { type = number - description = "Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" + description = "Allocated memory per member. For more information, see https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling" default = 4096 } @@ -89,15 +89,15 @@ variable "service_credential_names" { default = {} } -variable "tags" { +variable "access_tags" { type = list(string) - description = "Optional list of tags to be added to the MySQL instance." + description = "A list of access tags to apply to the MySQL instance created by the module, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial for more details" default = [] } -variable "access_tags" { +variable "tags" { type = list(string) - description = "A list of access tags to apply to the MySQL instance created by the module, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial for more details" + description = "Optional list of tags to be added to the MySQL instance." default = [] } @@ -151,7 +151,7 @@ variable "auto_scaling" { rate_units = optional(string, "mb") }) }) - 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://ibm.biz/autoscaling-considerations in the IBM Cloud Docs." + 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-mysql?topic=databases-for-mysql-autoscaling-mysql&interface=ui in the IBM Cloud Docs." default = null } @@ -169,6 +169,14 @@ variable "kms_key_crn" { type = string description = "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)." default = null + validation { + condition = anytrue([ + var.kms_key_crn == null, + can(regex(".*kms.*", var.kms_key_crn)), + can(regex(".*hs-crypto.*", var.kms_key_crn)), + ]) + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance." + } } variable "use_same_kms_key_for_backups" { @@ -181,6 +189,14 @@ variable "backup_encryption_key_crn" { type = string description = "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)." default = null + validation { + condition = anytrue([ + var.backup_encryption_key_crn == null, + can(regex(".*kms.*", var.backup_encryption_key_crn)), + can(regex(".*hs-crypto.*", var.backup_encryption_key_crn)), + ]) + error_message = "Value must be the KMS key CRN from a Key Protect or Hyper Protect Crypto Services instance in one of the supported backup regions." + } } variable "use_default_backup_encryption_key" { @@ -219,7 +235,7 @@ variable "cbr_rules" { })) }))) })) - description = "(Optional, list) List of CBR rules to create" + description = "(Optional, list) List of CBR rules to create, if operations is not set it will default to api-type:data-plane" default = [] # Validation happens in the rule module } diff --git a/modules/fscloud/version.tf b/modules/fscloud/version.tf index a492ce30..3b200e51 100644 --- a/modules/fscloud/version.tf +++ b/modules/fscloud/version.tf @@ -8,7 +8,7 @@ terraform { ibm = { source = "IBM-Cloud/ibm" # Use "greater than or equal to" range in modules - version = ">= 1.79.2" + version = ">=1.79.2, <2.0.0" } } } diff --git a/reference-architecture/deployable-architecture-mysql.svg b/reference-architecture/deployable-architecture-mysql.svg index 3c9b426c..190e14b5 100644 --- a/reference-architecture/deployable-architecture-mysql.svg +++ b/reference-architecture/deployable-architecture-mysql.svg @@ -1,4 +1,4 @@ -
IBM Cloud
IBM Cloud
KMS Encryption
KMS Encryption
Region
Region
Resource Group
Resource Group
IBM Cloud MySQL Instance
IBM Cloud MySQL Instance
MSQL
MSQL
Text is not SVG - cannot display
\ No newline at end of file +IBM CloudRegionResource GroupIBM Cloud MySQL Instance
[Optional] KMS
[Optional] KMS
Key Ring
Key Ring
mysql-key
Text is not SVG - cannot display
\ No newline at end of file diff --git a/solutions/standard/DA-types.md b/solutions/fully-configurable/DA-types.md similarity index 100% rename from solutions/standard/DA-types.md rename to solutions/fully-configurable/DA-types.md diff --git a/solutions/fully-configurable/README.md b/solutions/fully-configurable/README.md new file mode 100644 index 00000000..c007cda8 --- /dev/null +++ b/solutions/fully-configurable/README.md @@ -0,0 +1,3 @@ +# Cloud automation for MySQL (Fully configurable) + +:exclamation: **Important:** This solution is not intended to be called by other modules because it contains a provider configuration and is not compatible with the `for_each`, `count`, and `depends_on` arguments. For more information, see [Providers Within Modules](https://developer.hashicorp.com/terraform/language/modules/develop/providers). diff --git a/solutions/fully-configurable/catalogValidationValues.json.template b/solutions/fully-configurable/catalogValidationValues.json.template new file mode 100644 index 00000000..be8f591c --- /dev/null +++ b/solutions/fully-configurable/catalogValidationValues.json.template @@ -0,0 +1,9 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY, + "region": "us-south", + "resource_tags": $TAGS, + "name": $PREFIX, + "existing_resource_group_name": "geretain-test-mysql", + "existing_kms_instance_crn": $HPCS_US_SOUTH_CRN, + "kms_encryption_enabled": true +} diff --git a/solutions/standard/main.tf b/solutions/fully-configurable/main.tf similarity index 80% rename from solutions/standard/main.tf rename to solutions/fully-configurable/main.tf index 8af778df..7ea4ab00 100644 --- a/solutions/standard/main.tf +++ b/solutions/fully-configurable/main.tf @@ -1,12 +1,14 @@ ####################################################################################################################### # Resource Group ####################################################################################################################### +locals { + prefix = var.prefix != null ? trimspace(var.prefix) != "" ? "${var.prefix}-" : "" : "" +} 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 - existing_resource_group_name = var.use_existing_resource_group == true ? var.resource_group_name : null + existing_resource_group_name = var.existing_resource_group_name } ####################################################################################################################### @@ -14,9 +16,14 @@ module "resource_group" { ####################################################################################################################### locals { - create_new_kms_key = var.existing_mysql_instance_crn == null && !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 - mysql_key_name = var.prefix != null ? "${var.prefix}-${var.key_name}" : var.key_name - mysql_key_ring_name = var.prefix != null ? "${var.prefix}-${var.key_ring_name}" : var.key_ring_name + use_ibm_owned_encryption_key = !var.kms_encryption_enabled + create_new_kms_key = ( + var.kms_encryption_enabled && + var.existing_mysql_instance_crn == null && + var.existing_kms_key_crn == null + ) + mysql_key_name = "${local.prefix}${var.key_name}" + mysql_key_ring_name = "${local.prefix}${var.key_ring_name}" } module "kms" { @@ -41,7 +48,7 @@ module "kms" { standard_key = false rotation_interval_month = 3 dual_auth_delete_enabled = false - force_delete = true + force_delete = true # Force delete must be set to true, or the terraform destroy will fail since the service does not de-register itself from the key until the reclamation period has expired. } ] } @@ -85,23 +92,23 @@ 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.existing_mysql_instance_crn == null && !var.skip_mysql_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key - create_cross_account_backup_kms_auth_policy = var.existing_mysql_instance_crn == null && !var.skip_mysql_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.kms_encryption_enabled && !var.skip_mysql_kms_auth_policy && var.ibmcloud_kms_api_key != null + create_cross_account_backup_kms_auth_policy = var.kms_encryption_enabled && !var.skip_mysql_kms_auth_policy && var.ibmcloud_kms_api_key != null && 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.existing_mysql_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_mysql_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_mysql_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_mysql_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.mysql_key_ring_name, local.mysql_key_name)].crn - kms_key_id = var.existing_mysql_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.mysql_key_ring_name, local.mysql_key_name)].key_id - kms_region = var.existing_mysql_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 KMS encryption enabled (and existing MySql 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.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? 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.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? 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.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? 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.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? null : var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.mysql_key_ring_name, local.mysql_key_name)].crn + kms_key_id = !var.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? null : var.existing_kms_key_crn != null ? module.kms_key_crn_parser[0].resource : module.kms[0].keys[format("%s.%s", local.mysql_key_ring_name, local.mysql_key_name)].key_id + kms_region = !var.kms_encryption_enabled || var.existing_mysql_instance_crn != null ? 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.existing_mysql_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_backup_kms_key_crn + backup_kms_key_crn = var.existing_mysql_instance_crn != null || local.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 } @@ -266,24 +273,23 @@ data "ibm_database_connection" "existing_connection" { user_type = "database" } - # Create new instance module "mysql" { count = var.existing_mysql_instance_crn != null ? 0 : 1 - source = "../../modules/fscloud" + source = "../.." 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 - name = var.prefix != null ? "${var.prefix}-${var.name}" : var.name + name = "${local.prefix}${var.name}" region = var.region mysql_version = var.mysql_version - skip_iam_authorization_policy = var.skip_mysql_kms_auth_policy - use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key + skip_iam_authorization_policy = var.kms_encryption_enabled ? var.skip_mysql_kms_auth_policy : true + use_ibm_owned_encryption_key = local.use_ibm_owned_encryption_key kms_key_crn = local.kms_key_crn backup_encryption_key_crn = local.backup_kms_key_crn use_same_kms_key_for_backups = local.use_same_kms_key_for_backups use_default_backup_encryption_key = var.use_default_backup_encryption_key access_tags = var.access_tags - tags = var.tags + tags = var.resource_tags admin_pass = local.admin_pass users = var.users members = var.members @@ -295,6 +301,7 @@ module "mysql" { configuration = var.configuration service_credential_names = var.service_credential_names backup_crn = var.backup_crn + service_endpoints = var.service_endpoints remote_leader_crn = var.remote_leader_crn } @@ -307,14 +314,12 @@ locals { mysql_port = var.existing_mysql_instance_crn != null ? data.ibm_database_connection.existing_connection[0].mysql[0].hosts[0].port : module.mysql[0].port } - ####################################################################################################################### # Secrets management ####################################################################################################################### locals { - ## Variable validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) - create_sm_auth_policy = var.skip_mysql_secrets_manager_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 + create_secrets_manager_auth_policy = var.skip_mysql_secrets_manager_auth_policy || var.existing_secrets_manager_instance_crn == null ? 0 : 1 } # Parse the Secrets Manager CRN @@ -325,10 +330,9 @@ module "sm_instance_crn_parser" { crn = var.existing_secrets_manager_instance_crn } - # create a service authorization between Secrets Manager and the target service (Databases for MySQL) resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { - count = local.create_sm_auth_policy + count = local.create_secrets_manager_auth_policy source_service_name = "secrets-manager" source_resource_instance_id = local.existing_secrets_manager_instance_guid target_service_name = "databases-for-mysql" @@ -339,7 +343,7 @@ resource "ibm_iam_authorization_policy" "secrets_manager_key_manager" { # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 resource "time_sleep" "wait_for_mysql_authorization_policy" { - count = local.create_sm_auth_policy + count = local.create_secrets_manager_auth_policy depends_on = [ibm_iam_authorization_policy.secrets_manager_key_manager] create_duration = "30s" } @@ -369,10 +373,10 @@ locals { # 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_secrets_manager_secret_group != null ? "${var.prefix}-${var.admin_pass_secrets_manager_secret_group}" : var.admin_pass_secrets_manager_secret_group + secret_group_name = "${local.prefix}${var.admin_pass_secrets_manager_secret_group}" existing_secret_group = var.use_existing_admin_pass_secrets_manager_secret_group secrets = [{ - secret_name = (var.prefix != null && var.prefix != "") && var.admin_pass_secrets_manager_secret_name != null ? "${var.prefix}-${var.admin_pass_secrets_manager_secret_name}" : var.admin_pass_secrets_manager_secret_name + secret_name = "${local.prefix}${var.admin_pass_secrets_manager_secret_name}" secret_type = "arbitrary" secret_payload_password = local.admin_pass } diff --git a/solutions/standard/outputs.tf b/solutions/fully-configurable/outputs.tf similarity index 99% rename from solutions/standard/outputs.tf rename to solutions/fully-configurable/outputs.tf index 7ecaa2d2..f15b2605 100644 --- a/solutions/standard/outputs.tf +++ b/solutions/fully-configurable/outputs.tf @@ -21,6 +21,7 @@ output "crn" { description = "MySQL instance crn" value = local.mysql_crn } + output "service_credentials_json" { description = "Service credentials json map" value = var.existing_mysql_instance_crn != null ? null : module.mysql[0].service_credentials_json @@ -32,6 +33,7 @@ output "service_credentials_object" { value = var.existing_mysql_instance_crn != null ? null : module.mysql[0].service_credentials_object sensitive = true } + output "hostname" { description = "Database connection hostname" value = local.mysql_hostname @@ -41,6 +43,7 @@ output "port" { description = "Database connection port" value = local.mysql_port } + output "secrets_manager_secrets" { description = "Service credential secrets" value = length(local.service_credential_secrets) > 0 ? module.secrets_manager_service_credentials[0].secrets : null diff --git a/solutions/fully-configurable/provider.tf b/solutions/fully-configurable/provider.tf new file mode 100644 index 00000000..e66dac2c --- /dev/null +++ b/solutions/fully-configurable/provider.tf @@ -0,0 +1,14 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region + visibility = var.provider_visibility + private_endpoint_type = (var.provider_visibility == "private" && var.region == "ca-mon") ? "vpe" : null +} + +provider "ibm" { + alias = "kms" + ibmcloud_api_key = var.ibmcloud_kms_api_key != null ? var.ibmcloud_kms_api_key : var.ibmcloud_api_key + region = local.kms_region + visibility = var.provider_visibility + private_endpoint_type = (var.provider_visibility == "private" && var.region == "ca-mon") ? "vpe" : null +} diff --git a/solutions/standard/variables.tf b/solutions/fully-configurable/variables.tf similarity index 71% rename from solutions/standard/variables.tf rename to solutions/fully-configurable/variables.tf index 71a7cab0..52eeb640 100644 --- a/solutions/standard/variables.tf +++ b/solutions/fully-configurable/variables.tf @@ -7,21 +7,39 @@ variable "ibmcloud_api_key" { description = "The IBM Cloud API key to deploy resources." sensitive = true } -variable "use_existing_resource_group" { - type = bool - description = "Whether to use an existing resource group." - default = false -} -variable "resource_group_name" { +variable "existing_resource_group_name" { type = string - description = "The name of a new or an existing resource group to provision the Databases for MySQL in. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + description = "The name of an existing resource group to provision resource in." + default = "Default" + nullable = false } variable "prefix" { type = string - description = "Prefix to add to all resources created by this solution." - default = null + nullable = true + description = "The prefix to be added to all resources created by this solution. To skip using a prefix, set this value to null or an empty string. The prefix must begin with a lowercase letter and may contain only lowercase letters, digits, and hyphens '-'. It should not exceed 16 characters, must not end with a hyphen('-'), and can not contain consecutive hyphens ('--'). Example: prod-0205-cos. [Learn more](https://terraform-ibm-modules.github.io/documentation/#/prefix.md)." + + validation { + # - null and empty string is allowed + # - Must not contain consecutive hyphens (--): length(regexall("--", var.prefix)) == 0 + # - Starts with a lowercase letter: [a-z] + # - Contains only lowercase letters (a–z), digits (0–9), and hyphens (-) + # - Must not end with a hyphen (-): [a-z0-9] + condition = (var.prefix == null || var.prefix == "" ? true : + alltrue([ + can(regex("^[a-z][-a-z0-9]*[a-z0-9]$", var.prefix)), + length(regexall("--", var.prefix)) == 0 + ]) + ) + error_message = "Prefix must begin with a lowercase letter and may contain only lowercase letters, digits, and hyphens '-'. It must not end with a hyphen('-'), and cannot contain consecutive hyphens ('--')." + } + + validation { + # must not exceed 16 characters in length + condition = length(var.prefix) <= 16 + error_message = "Prefix must not exceed 16 characters." + } } variable "name" { @@ -34,6 +52,7 @@ variable "region" { description = "The region where you want to deploy your instance." type = string default = "us-south" + validation { condition = var.existing_mysql_instance_crn != null && var.region != local.existing_mysql_region ? false : true error_message = "The region detected in the 'existing_mysql_instance_crn' value must match the value of the 'region' input variable when passing an existing instance." @@ -44,18 +63,17 @@ variable "existing_mysql_instance_crn" { type = string default = null description = "The CRN of an existing Databases for MySql instance. If no value is specified, a new instance is created." - } -variable "remote_leader_crn" { +variable "mysql_version" { + description = "The version of the Databases for MySQL instance." type = string - description = "A CRN of the leader database to make the replica(read-only) deployment. The leader database is created by a database deployment with the same service ID. A read-only replica is set up to replicate all of your data from the leader deployment to the replica deployment by using asynchronous replication. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-read-replicas)" default = null } -variable "mysql_version" { - description = "The version of the Databases for MySQL instance. If no value is specified, the current preferred version of Databases for MySQL is used." +variable "remote_leader_crn" { type = string + description = "A CRN of the leader database to make the replica(read-only) deployment. The leader database is created by a database deployment with the same service ID. A read-only replica is set up to replicate all of your data from the leader deployment to the replica deployment by using asynchronous replication. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-read-replicas)" default = null } @@ -63,6 +81,17 @@ variable "mysql_version" { # ICD hosting model properties ############################################################################## +variable "service_endpoints" { + type = string + description = "The type of endpoint of the database instance. Possible values: `public`, `private`, `public-and-private`." + default = "private" + + 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 "members" { type = number description = "The number of members that are allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)." @@ -78,7 +107,7 @@ variable "member_memory_mb" { variable "member_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-mysql?topic=databases-for-mysql-resources-scaling)." - default = 3 + default = 0 } variable "member_disk_mb" { @@ -93,45 +122,8 @@ variable "member_host_flavor" { default = "multitenant" } -variable "service_credential_names" { - description = "Map of name, role for service credentials that you want to create for the database. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/standard/DA-types.md#svc-credential-name)" - type = map(string) - default = {} -} - -variable "admin_pass" { - type = string - description = "The password for the database administrator. If the admin password is null then the admin user ID cannot be accessed. More users can be specified in a user block." - default = null - sensitive = true -} - -variable "users" { - type = list(object({ - name = string - password = string # pragma: allowlist secret - type = string # "type" is required to generate the connection string for the outputs. - role = optional(string) - })) - default = [] - sensitive = true - description = "A list of users that you want to create on the database. Users block is supported by MySQL version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the MySQL instance. This blocks creates native MySQL database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/standard/DA-types.md#users)" -} - -variable "tags" { - type = list(any) - description = "The list of tags to be added to the Databases for MySQL instance." - default = [] -} - -variable "access_tags" { - type = list(string) - description = "A list of access tags to apply to the Databases for MySQL instance created by the solution. [Learn more](https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial)." - default = [] -} - variable "configuration" { - description = "Database Configuration for MySQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/standard/DA-types.md)" + description = "Database Configuration for MySQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/fully-configurable/DA-types.md)" type = object({ default_authentication_plugin = optional(string) # sha256_password,caching_sha2_password,mysql_native_password innodb_buffer_pool_size_percentage = optional(number) # 10 ≤ value ≤ 100 @@ -170,47 +162,77 @@ variable "configuration" { } } +variable "service_credential_names" { + description = "Map of name, role for service credentials that you want to create for the database. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#svc-credential-name)" + type = map(string) + default = {} +} + +variable "admin_pass" { + type = string + description = "The password for the database administrator. If no admin password is provided (i.e., it is null), one will be generated automatically. Additional users can be added using a user block." + default = null + sensitive = true +} + +variable "users" { + type = list(object({ + name = string + password = string # pragma: allowlist secret + type = string # "type" is required to generate the connection string for the outputs. + role = optional(string) + })) + default = [] + sensitive = true + description = "A list of users that you want to create on the database. Users block is supported by MySQL version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the MySQL instance. This blocks creates native MySQL database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#users)" +} + +variable "resource_tags" { + type = list(string) + description = "The list of resource tags to be added to the Databases for MySQL instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the Databases for MySQL instance created by the solution. [Learn more](https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial)." + default = [] +} + ############################################################## # Encryption ############################################################## -variable "use_ibm_owned_encryption_key" { +variable "kms_encryption_enabled" { type = bool - description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for `existing_kms_instance_crn` to create a new key, or `existing_kms_key_crn` and/or `existing_backup_kms_key_crn` to use an existing key." + description = "Set to true to enable KMS encryption using customer-managed keys. When enabled, you must provide a value for at least one of the following: existing_kms_instance_crn, existing_kms_key_crn, or existing_backup_kms_key_crn. If set to false, IBM-owned encryption is used (i.e., encryption keys managed and held by IBM)." default = false + validation { - condition = ( + condition = (!var.kms_encryption_enabled || var.existing_mysql_instance_crn != null || - !(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 - )) + var.existing_kms_instance_crn != null || + var.existing_kms_key_crn != null || + var.existing_backup_kms_key_crn != null ) - error_message = "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." + error_message = "When 'kms_encryption_enabled' is true, you must provide either 'existing_backup_kms_key_crn', 'existing_kms_instance_crn' (to create a new key) or 'existing_kms_key_crn' (to use an existing key)." } - # this validation ensures key info is provided when IBM-owned key is disabled and no Redis instance is given validation { - condition = !( - var.existing_mysql_instance_crn == null && - var.use_ibm_owned_encryption_key == false && - var.existing_kms_instance_crn == null && - var.existing_kms_key_crn == null - ) - error_message = "When 'use_ibm_owned_encryption_key' is false, you must provide either 'existing_kms_instance_crn' (to create a new key) or 'existing_kms_key_crn' (to use an existing key)." + condition = (var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null && var.existing_backup_kms_key_crn == null) || var.kms_encryption_enabled + error_message = "When either 'existing_kms_instance_crn', 'existing_kms_key_crn' or 'existing_backup_kms_key_crn' is set then 'kms_encryption_enabled' must be set to true." } } variable "existing_kms_instance_crn" { type = string - description = "The CRN of a Key Protect or Hyper Protect Crypto Services instance. Required to create a new encryption key and key ring which will be used to encrypt both deployment data and backups. Applies only if `use_ibm_owned_encryption_key` is false. To use an existing key, pass values for `existing_kms_key_crn` and/or `existing_backup_kms_key_crn`. 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)." + description = "The CRN of a Key Protect or Hyper Protect Crypto Services instance. Required to create a new encryption key and key ring which will be used to encrypt both deployment data and backups. To use an existing key, pass values for `existing_kms_key_crn` and/or `existing_backup_kms_key_crn`. 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)." default = null } variable "existing_kms_key_crn" { type = string - description = "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 optional `existing_backup_kms_key_crn` input. If no value is passed a new key will be created in the instance specified in the `existing_kms_instance_crn` input. 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)." + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. By default this key is used for both deployment data and backups, but this behaviour can be altered using the optional `existing_backup_kms_key_crn` input. If no value is passed a new key will be created in the instance specified in the `existing_kms_instance_crn` input. 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)." default = null } @@ -226,7 +248,7 @@ variable "kms_endpoint_type" { variable "skip_mysql_kms_auth_policy" { type = bool - description = "Whether to create an IAM authorization policy that permits all Databases for MySQL instances in the resource group to read the encryption key from the Hyper Protect Crypto Services instance specified in the `existing_kms_instance_crn` variable." + description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for MySQL instances in the given resource group 'Reader' access to the Key Protect or Hyper Protect Crypto Services key. 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 `kms_encryption_enabled` is false." default = false } @@ -251,13 +273,13 @@ variable "key_name" { variable "existing_backup_kms_key_crn" { type = string - description = "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. If no value is passed, the value of `existing_kms_key_crn` is used. If no value is passed for `existing_kms_key_crn`, a new key will be created in the instance specified in the `existing_kms_instance_crn` input. 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)." + description = "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. If no value is passed, the value of `existing_kms_key_crn` is used. If no value is passed for `existing_kms_key_crn`, a new key will be created in the instance specified in the `existing_kms_instance_crn` input. 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)." default = null } variable "use_default_backup_encryption_key" { type = bool - description = "When `use_ibm_owned_encryption_key` is set to false, backups will be encrypted with either the key specified in `existing_kms_key_crn`, in `existing_backup_kms_key_crn`, or with a new key that will be created in the instance specified in the `existing_kms_instance_crn` input. 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." + description = "When `kms_encryption_enabled` is set to true, backups will be encrypted with either the key specified in `existing_kms_key_crn`, in `existing_backup_kms_key_crn`, or with a new key that will be created in the instance specified in the `existing_kms_instance_crn` input. 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 `kms_encryption_enabled` to false to use the default encryption for both backups and deployment data." default = false } @@ -274,6 +296,7 @@ variable "backup_crn" { error_message = "backup_crn must be null OR starts with 'crn:' and contains ':backup:'" } } + variable "provider_visibility" { description = "Set the visibility value for the IBM terraform provider. Supported values are `public`, `private`, `public-and-private`. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/guides/custom-service-endpoints)." type = string @@ -312,19 +335,18 @@ variable "auto_scaling" { rate_units = optional(string, "mb") }) }) - 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. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/standard/DA-types.md#autoscaling)" + 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. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#autoscaling)" default = null } -############################################################################## -## Secrets Manager Service Credentials -############################################################################## +############################################################################# +# Secrets Manager Service Credentials +############################################################################# variable "existing_secrets_manager_instance_crn" { type = string default = null description = "The CRN of existing secrets manager to use to create service credential secrets for Databases for MySQL instance." - } variable "existing_secrets_manager_endpoint_type" { @@ -342,7 +364,7 @@ variable "service_credential_secrets" { secret_group_name = string secret_group_description = optional(string) existing_secret_group = optional(bool) - service_credentials = list(object({ + service_credentials = list(object({ # pragma: allowlist secret secret_name = string service_credentials_source_service_role_crn = string secret_labels = optional(list(string)) @@ -355,7 +377,7 @@ variable "service_credential_secrets" { })) })) default = [] - description = "Service credential secrets configuration for Databases for MySQL. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/standard/DA-types.md#service-credential-secrets)." + description = "Service credential secrets configuration for Databases for MySQL. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/fully-configurable/DA-types.md#service-credential-secrets)." validation { # Service roles CRNs can be found at https://cloud.ibm.com/iam/roles, select the IBM Cloud Database and select the role @@ -367,6 +389,7 @@ variable "service_credential_secrets" { ]) error_message = "service_credentials_source_service_role_crn must be a serviceRole CRN. See https://cloud.ibm.com/iam/roles" } + validation { condition = ( length(var.service_credential_secrets) == 0 || @@ -400,12 +423,11 @@ variable "use_existing_admin_pass_secrets_manager_secret_group" { type = bool description = "Whether to use an existing secrets manager secret group for admin password." default = false - } variable "admin_pass_secrets_manager_secret_name" { type = string - description = "The name of a new redis administrator secret. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + description = "The name of a new MySQL administrator secret. If a prefix input variable is specified, the prefix is added to the name in the `-` format." default = "mysql-admin-password" validation { diff --git a/solutions/standard/version.tf b/solutions/fully-configurable/version.tf similarity index 100% rename from solutions/standard/version.tf rename to solutions/fully-configurable/version.tf diff --git a/solutions/security-enforced/README.md b/solutions/security-enforced/README.md new file mode 100644 index 00000000..f1c84770 --- /dev/null +++ b/solutions/security-enforced/README.md @@ -0,0 +1,3 @@ +# Cloud automation for MySQL (Security-enforced) + +:exclamation: **Important:** This solution is not intended to be called by other modules because it contains a provider configuration and is not compatible with the `for_each`, `count`, and `depends_on` arguments. For more information, see [Providers Within Modules](https://developer.hashicorp.com/terraform/language/modules/develop/providers). diff --git a/solutions/standard/catalogValidationValues.json.template b/solutions/security-enforced/catalogValidationValues.json.template similarity index 62% rename from solutions/standard/catalogValidationValues.json.template rename to solutions/security-enforced/catalogValidationValues.json.template index e69e502f..cbc61a42 100644 --- a/solutions/standard/catalogValidationValues.json.template +++ b/solutions/security-enforced/catalogValidationValues.json.template @@ -1,8 +1,8 @@ { "ibmcloud_api_key": $VALIDATION_APIKEY, "region": "us-south", - "tags": $TAGS, + "resource_tags": $TAGS, "name": $PREFIX, - "resource_group_name": $PREFIX, + "existing_resource_group_name": "geretain-test-mysql", "existing_kms_instance_crn": $HPCS_US_SOUTH_CRN } diff --git a/solutions/security-enforced/main.tf b/solutions/security-enforced/main.tf new file mode 100644 index 00000000..bc2ddb85 --- /dev/null +++ b/solutions/security-enforced/main.tf @@ -0,0 +1,43 @@ +module "mysql" { + source = "../fully-configurable" + ibmcloud_api_key = var.ibmcloud_api_key + existing_resource_group_name = var.existing_resource_group_name + prefix = var.prefix + name = var.name + provider_visibility = "private" + region = var.region + existing_mysql_instance_crn = var.existing_mysql_instance_crn + mysql_version = var.mysql_version + members = var.members + member_memory_mb = var.member_memory_mb + member_cpu_count = var.member_cpu_count + member_disk_mb = var.member_disk_mb + member_host_flavor = var.member_host_flavor + service_credential_names = var.service_credential_names + admin_pass = var.admin_pass + users = var.users + configuration = var.configuration + resource_tags = var.resource_tags + access_tags = var.access_tags + kms_encryption_enabled = true + existing_kms_instance_crn = var.existing_kms_instance_crn + existing_kms_key_crn = var.existing_kms_key_crn + kms_endpoint_type = "private" + skip_mysql_kms_auth_policy = var.skip_mysql_kms_auth_policy + ibmcloud_kms_api_key = var.ibmcloud_kms_api_key + key_ring_name = var.key_ring_name + key_name = var.key_name + existing_backup_kms_key_crn = var.existing_backup_kms_key_crn + use_default_backup_encryption_key = false + backup_crn = var.backup_crn + auto_scaling = var.auto_scaling + existing_secrets_manager_instance_crn = var.existing_secrets_manager_instance_crn + existing_secrets_manager_endpoint_type = "private" + service_credential_secrets = var.service_credential_secrets + skip_mysql_secrets_manager_auth_policy = var.skip_mysql_secrets_manager_auth_policy + admin_pass_secrets_manager_secret_group = var.admin_pass_secrets_manager_secret_group + use_existing_admin_pass_secrets_manager_secret_group = var.use_existing_admin_pass_secrets_manager_secret_group + admin_pass_secrets_manager_secret_name = var.admin_pass_secrets_manager_secret_name + service_endpoints = "private" + remote_leader_crn = var.remote_leader_crn +} diff --git a/solutions/security-enforced/outputs.tf b/solutions/security-enforced/outputs.tf new file mode 100644 index 00000000..06f74210 --- /dev/null +++ b/solutions/security-enforced/outputs.tf @@ -0,0 +1,50 @@ +############################################################################## +# Outputs +############################################################################## + +output "id" { + description = "MySQL instance id" + value = module.mysql.id +} + +output "version" { + description = "MySQL instance version" + value = module.mysql.version +} + +output "guid" { + description = "MySQL instance guid" + value = module.mysql.guid +} + +output "crn" { + description = "MySQL instance crn" + value = module.mysql.crn +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.mysql.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.mysql.service_credentials_object + sensitive = true +} + +output "hostname" { + description = "Database connection hostname" + value = module.mysql.hostname +} + +output "port" { + description = "Database connection port" + value = module.mysql.port +} + +output "secrets_manager_secrets" { + description = "Service credential secrets" + value = module.mysql.secrets_manager_secrets +} diff --git a/solutions/security-enforced/provider.tf b/solutions/security-enforced/provider.tf new file mode 100644 index 00000000..4c6add22 --- /dev/null +++ b/solutions/security-enforced/provider.tf @@ -0,0 +1 @@ +# Explicit provider config not required here as provider config in fully-configurable is used diff --git a/solutions/security-enforced/variables.tf b/solutions/security-enforced/variables.tf new file mode 100644 index 00000000..5e291f69 --- /dev/null +++ b/solutions/security-enforced/variables.tf @@ -0,0 +1,375 @@ +############################################################################## +# Input Variables +############################################################################## + +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API key to deploy resources." + sensitive = true +} + +variable "existing_resource_group_name" { + type = string + description = "The name of an existing resource group to provision resource in." + default = "Default" + nullable = false +} + +variable "prefix" { + type = string + nullable = true + description = "The prefix to be added to all resources created by this solution. To skip using a prefix, set this value to null or an empty string. The prefix must begin with a lowercase letter and may contain only lowercase letters, digits, and hyphens '-'. It should not exceed 16 characters, must not end with a hyphen('-'), and can not contain consecutive hyphens ('--'). Example: prod-0205-cos. [Learn more](https://terraform-ibm-modules.github.io/documentation/#/prefix.md)." + + validation { + # - null and empty string is allowed + # - Must not contain consecutive hyphens (--): length(regexall("--", var.prefix)) == 0 + # - Starts with a lowercase letter: [a-z] + # - Contains only lowercase letters (a–z), digits (0–9), and hyphens (-) + # - Must not end with a hyphen (-): [a-z0-9] + condition = (var.prefix == null || var.prefix == "" ? true : + alltrue([ + can(regex("^[a-z][-a-z0-9]*[a-z0-9]$", var.prefix)), + length(regexall("--", var.prefix)) == 0 + ]) + ) + error_message = "Prefix must begin with a lowercase letter and may contain only lowercase letters, digits, and hyphens '-'. It must not end with a hyphen('-'), and cannot contain consecutive hyphens ('--')." + } + + validation { + # must not exceed 16 characters in length + condition = length(var.prefix) <= 16 + error_message = "Prefix must not exceed 16 characters." + } +} + +variable "name" { + type = string + description = "The name of the Databases for MySQL instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "mysql" +} + +variable "region" { + description = "The region where you want to deploy your instance." + type = string + default = "us-south" +} + +variable "existing_mysql_instance_crn" { + type = string + default = null + description = "The CRN of an existing Databases for MySql instance. If no value is specified, a new instance is created." +} + +variable "mysql_version" { + description = "The version of the Databases for MySQL instance." + type = string + default = null +} + +variable "remote_leader_crn" { + type = string + description = "A CRN of the leader database to make the replica(read-only) deployment. The leader database is created by a database deployment with the same service ID. A read-only replica is set up to replicate all of your data from the leader deployment to the replica deployment by using asynchronous replication. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-read-replicas)" + default = null +} + +############################################################################## +# ICD hosting model properties +############################################################################## + +variable "members" { + type = number + description = "The number of members that are allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)." + default = 3 +} + +variable "member_memory_mb" { + type = number + description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" + default = 4096 +} + +variable "member_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-mysql?topic=databases-for-mysql-resources-scaling)." + default = 0 +} + +variable "member_disk_mb" { + type = number + description = "The disk that is allocated per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)." + default = 10240 +} + +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" +} + +variable "configuration" { + description = "Database Configuration for MySQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/fully-configurable/DA-types.md#configuration)" + type = object({ + default_authentication_plugin = optional(string) # sha256_password,caching_sha2_password,mysql_native_password + innodb_buffer_pool_size_percentage = optional(number) # 10 ≤ value ≤ 100 + innodb_flush_log_at_trx_commit = optional(number) # 0 ≤ value ≤ 2 + innodb_log_buffer_size = optional(number) # 1048576 ≤ value ≤ 4294967295 + innodb_log_file_size = optional(number) # 4194304 ≤ value ≤ 274877906900 + innodb_lru_scan_depth = optional(number) # 128 ≤ value ≤ 2048 + innodb_read_io_threads = optional(number) # 1 ≤ value ≤ 64 + innodb_write_io_threads = optional(number) # 1 ≤ value ≤ 64 + max_allowed_packet = optional(number) # 1024 ≤ value ≤ 1073741824 + max_connections = optional(number) # 100 ≤ value ≤ 200000 + max_prepared_stmt_count = optional(number) # 0 ≤ value ≤ 4194304 + mysql_max_binlog_age_sec = optional(number) # 300 ≤ value ≤ 1073741823 Default: 1800 + net_read_timeout = optional(number) # 1 ≤ value ≤ 7200 + net_write_timeout = optional(number) # 1 ≤ value ≤ 7200 + sql_mode = optional(string) # The comma-separated list of SQL modes applied on this server globally. + wait_timeout = optional(number) # 1 ≤ value ≤ 31536000 + }) + default = { + default_authentication_plugin = "sha256_password" + innodb_buffer_pool_size_percentage = 50 + innodb_flush_log_at_trx_commit = 2 + innodb_log_buffer_size = 33554432 + innodb_log_file_size = 104857600 + innodb_lru_scan_depth = 256 + innodb_read_io_threads = 4 + innodb_write_io_threads = 4 + max_allowed_packet = 16777216 + max_connections = 200 + max_prepared_stmt_count = 16382 + mysql_max_binlog_age_sec = 1800 + net_read_timeout = 60 + net_write_timeout = 60 + # sql_mode No sensible default set of modes https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html + wait_timeout = 28800 + } +} + +variable "service_credential_names" { + description = "Map of name, role for service credentials that you want to create for the database. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#svc-credential-name)" + type = map(string) + default = {} +} + +variable "admin_pass" { + type = string + description = "The password for the database administrator. If no admin password is provided (i.e., it is null), one will be generated automatically. Additional users can be added using a user block." + default = null + sensitive = true +} + +variable "users" { + type = list(object({ + name = string + password = string # pragma: allowlist secret + type = string # "type" is required to generate the connection string for the outputs. + role = optional(string) + })) + default = [] + sensitive = true + description = "A list of users that you want to create on the database. Users block is supported by MySQL version >= 6.0. Multiple blocks are allowed. The user password must be in the range of 10-32 characters. Be warned that in most case using IAM service credentials (via the var.service_credential_names) is sufficient to control access to the MySQL instance. This blocks creates native MySQL database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#users)" +} + +variable "resource_tags" { + type = list(string) + description = "The list of resource tags to be added to the Databases for MySQL instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the Databases for MySQL instance created by the solution. [Learn more](https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial)." + default = [] +} + +############################################################## +# Encryption +############################################################## + +variable "existing_kms_instance_crn" { + type = string + description = "The CRN of a Key Protect or Hyper Protect Crypto Services instance. Required to create a new encryption key and key ring which will be used to encrypt both deployment data and backups. To use an existing key, pass values for `existing_kms_key_crn` and/or `existing_backup_kms_key_crn`. 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)." + default = null +} + +variable "existing_kms_key_crn" { + type = string + description = "The CRN of a Key Protect or Hyper Protect Crypto Services encryption key to encrypt your data. By default this key is used for both deployment data and backups, but this behaviour can be altered using the optional `existing_backup_kms_key_crn` input. If no value is passed a new key will be created in the instance specified in the `existing_kms_instance_crn` input. 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)." + default = null + + + validation { + condition = ( + (var.existing_kms_key_crn != null && var.existing_kms_instance_crn == null) || + (var.existing_kms_key_crn == null && var.existing_kms_instance_crn != null) + ) + error_message = "Either existing_kms_key_crn or existing_kms_instance_crn must be set, but not both." + } +} + +variable "skip_mysql_kms_auth_policy" { + type = bool + description = "Whether to create an IAM authorization policy that permits all Databases for MySQL instances in the resource group to read the encryption key from the Hyper Protect Crypto Services instance specified in the `existing_kms_instance_crn` variable." + default = false +} + +variable "ibmcloud_kms_api_key" { + type = string + description = "The IBM Cloud API key that can create a root key and key ring in the key management service (KMS) instance. If not specified, the 'ibmcloud_api_key' variable is used. Specify this key if the instance in `existing_kms_instance_crn` is in an account that's different from the MySQL instance. Leave this input empty if the same account owns both instances." + sensitive = true + default = null +} + +variable "key_ring_name" { + type = string + default = "mysql-key-ring" + description = "The name for the key ring created for the Databases for MySQL key. Applies only if not specifying an existing key. If a prefix input variable is specified, the prefix is added to the name in the `-` format." +} + +variable "key_name" { + type = string + default = "mysql-key" + description = "The name for the key created for the Databases for MySQL key. Applies only if not specifying an existing key. If a prefix input variable is specified, the prefix is added to the name in the `-` format." +} + +variable "existing_backup_kms_key_crn" { + type = string + description = "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. If no value is passed, the value of `existing_kms_key_crn` is used. If no value is passed for `existing_kms_key_crn`, a new key will be created in the instance specified in the `existing_kms_instance_crn` input." + default = null +} + +variable "backup_crn" { + type = string + description = "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." + default = null + + validation { + condition = anytrue([ + var.backup_crn == null, + can(regex("^crn:.*:backup:", var.backup_crn)) + ]) + error_message = "backup_crn must be null OR starts with 'crn:' and contains ':backup:'" + } +} + +############################################################## +# Auto Scaling +############################################################## + +variable "auto_scaling" { + type = 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") + }) + }) + 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. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/blob/main/solutions/fully-configurable/DA-types.md#autoscaling)" + default = null +} + +############################################################################# +# Secrets Manager Service Credentials +############################################################################# + +variable "existing_secrets_manager_instance_crn" { + type = string + default = null + description = "The CRN of existing secrets manager to use to create service credential secrets for Databases for MySQL instance." +} + +variable "service_credential_secrets" { + type = list(object({ + secret_group_name = string + secret_group_description = optional(string) + existing_secret_group = optional(bool) + service_credentials = list(object({ # pragma: allowlist secret + secret_name = string + service_credentials_source_service_role_crn = string + secret_labels = optional(list(string)) + secret_auto_rotation = optional(bool) + secret_auto_rotation_unit = optional(string) + secret_auto_rotation_interval = optional(number) + service_credentials_ttl = optional(string) + service_credential_secret_description = optional(string) + + })) + })) + default = [] + description = "Service credential secrets configuration for Databases for MySQL. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-mysql/tree/main/solutions/fully-configurable/DA-types.md#service-credential-secrets)." + + validation { + # Service roles CRNs can be found at https://cloud.ibm.com/iam/roles, select the IBM Cloud Database and select the role + condition = alltrue([ + for group in var.service_credential_secrets : alltrue([ + # crn:v?:bluemix; two non-empty segments; three possibly empty segments; :serviceRole or role: non-empty segment + for credential in group.service_credentials : can(regex("^crn:v[0-9]:bluemix(:..*){2}(:.*){3}:(serviceRole|role):..*$", credential.service_credentials_source_service_role_crn)) + ]) + ]) + error_message = "service_credentials_source_service_role_crn must be a serviceRole CRN. See https://cloud.ibm.com/iam/roles" + } + + validation { + condition = ( + length(var.service_credential_secrets) == 0 || + var.existing_secrets_manager_instance_crn != null + ) + error_message = "`existing_secrets_manager_instance_crn` is required when adding service credentials to a secrets manager secret." + } +} + +variable "skip_mysql_secrets_manager_auth_policy" { + type = bool + default = false + description = "Whether an IAM authorization policy is created for Secrets Manager instance to create a service credential secrets for Databases for MySQL. If set to false, the Secrets Manager instance passed by the user is granted the Key Manager access to the MySQL 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." +} + +variable "admin_pass_secrets_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 = "mysql-secrets" + + validation { + condition = ( + var.existing_secrets_manager_instance_crn == null || + var.admin_pass_secrets_manager_secret_group != null + ) + error_message = "`admin_pass_secrets_manager_secret_group` is required when `existing_secrets_manager_instance_crn` is set." + } +} + +variable "use_existing_admin_pass_secrets_manager_secret_group" { + type = bool + description = "Whether to use an existing secrets manager secret group for admin password." + default = false +} + +variable "admin_pass_secrets_manager_secret_name" { + type = string + description = "The name of a new MySQL administrator secret. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "mysql-admin-password" + + validation { + condition = ( + var.existing_secrets_manager_instance_crn == null || + var.admin_pass_secrets_manager_secret_name != null + ) + error_message = "`admin_pass_secrets_manager_secret_name` is required when `existing_secrets_manager_instance_crn` is set." + } +} diff --git a/solutions/security-enforced/version.tf b/solutions/security-enforced/version.tf new file mode 100644 index 00000000..70b38b5b --- /dev/null +++ b/solutions/security-enforced/version.tf @@ -0,0 +1,6 @@ +terraform { + required_version = ">= 1.9.0" + # Lock DA into an exact provider version - renovate automation will keep it updated + required_providers { + } +} diff --git a/solutions/standard/README.md b/solutions/standard/README.md deleted file mode 100644 index 99cec06f..00000000 --- a/solutions/standard/README.md +++ /dev/null @@ -1,13 +0,0 @@ - # IBM Cloud Databases for MySQL - -This architecture creates an instance of IBM Cloud Databases for MySQL and supports provisioning of the following resources: - -- A resource group, if one is not passed in. -- A KMS root key, if one is not passed in. -- An IBM Cloud Databases for MySQL instance with KMS encryption. -- Autoscaling rules for the database instance, if provided. -- Service credential secrets and store them in secret manager. - -![fscloud-mysql](../../reference-architecture/deployable-architecture-mysql.svg) - -:exclamation: **Important:** This solution is not intended to be called by other modules because it contains a provider configuration and is not compatible with the `for_each`, `count`, and `depends_on` arguments. For more information, see [Providers Within Modules](https://developer.hashicorp.com/terraform/language/modules/develop/providers). diff --git a/solutions/standard/moved.tf b/solutions/standard/moved.tf deleted file mode 100644 index 590f868d..00000000 --- a/solutions/standard/moved.tf +++ /dev/null @@ -1,4 +0,0 @@ -moved { - from = module.mysql - to = module.mysql[0] -} diff --git a/solutions/standard/provider.tf b/solutions/standard/provider.tf deleted file mode 100644 index 409f5548..00000000 --- a/solutions/standard/provider.tf +++ /dev/null @@ -1,11 +0,0 @@ -provider "ibm" { - alias = "kms" - ibmcloud_api_key = var.ibmcloud_kms_api_key != null ? var.ibmcloud_kms_api_key : var.ibmcloud_api_key - region = local.kms_region - visibility = var.provider_visibility -} -provider "ibm" { - ibmcloud_api_key = var.ibmcloud_api_key - region = var.region - visibility = var.provider_visibility -} diff --git a/tests/other_test.go b/tests/other_test.go index 36b6e074..7ba0282a 100644 --- a/tests/other_test.go +++ b/tests/other_test.go @@ -3,9 +3,10 @@ package test import ( "fmt" + "testing" + "github.com/stretchr/testify/assert" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" - "testing" ) func TestRunRestoredDBExample(t *testing.T) { diff --git a/tests/pr_test.go b/tests/pr_test.go index f6fee672..eb0ad169 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -6,11 +6,9 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io/fs" "log" - mathrand "math/rand" + "math/big" "os" - "path/filepath" "strings" "testing" @@ -26,23 +24,22 @@ import ( "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) -const standardSolutionTerraformDir = "solutions/standard" -const fscloudExampleTerraformDir = "examples/fscloud" +const fullyConfigurableSolutionTerraformDir = "solutions/fully-configurable" +const securityEnforcedTerraformDir = "solutions/security-enforced" const latestVersion = "8.0" // Use existing resource group const resourceGroup = "geretain-test-mysql" -// Restricting due to limited availability of BYOK in certain regions +// Set up tests to only use supported BYOK regions const regionSelectionPath = "../common-dev-assets/common-go-assets/icd-region-prefs.yaml" // Define a struct with fields that match the structure of the YAML data const yamlLocation = "../common-dev-assets/common-go-assets/common-permanent-resources.yaml" -var permanentResources map[string]interface{} +var permanentResources map[string]any var sharedInfoSvc *cloudinfo.CloudInfoService - var validICDRegions = []string{ "eu-de", "us-south", @@ -61,87 +58,94 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -type tarIncludePatterns struct { - excludeDirs []string - - includeFiletypes []string +// Test the fully-configurable DA with defaults (no KMS encryption) +func TestRunFullyConfigurableSolutionSchematics(t *testing.T) { + t.Parallel() + prefix := "mysqlfc" + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), + fmt.Sprintf("%s/*.sh", "scripts"), + }, + TemplateFolder: fullyConfigurableSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: prefix, + // ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + CheckApplyResultForUpgrade: true, + WaitJobCompleteMinutes: 60, + }) - includeDirs []string -} + serviceCredentialSecrets := []map[string]any{ + { + "secret_group_name": fmt.Sprintf("%s-secret-group", options.Prefix), + "service_credentials": []map[string]string{ + { + "secret_name": fmt.Sprintf("%s-cred-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Viewer", + }, + { + "secret_name": fmt.Sprintf("%s-cred-writer", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Editor", + }, + }, + }, + } -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 + serviceCredentialNames := map[string]string{ + "admin": "Administrator", + "user1": "Viewer", + "user2": "Editor", } - return r.includeDirs, nil -} -func walk(r *tarIncludePatterns, s string, d fs.DirEntry, err error) error { + serviceCredentialNamesJSON, err := json.Marshal(serviceCredentialNames) if err != nil { - return err + log.Fatalf("Error converting to JSON: %s", 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, "../", "")) - } + + 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_resource_group_name", Value: resourceGroup, DataType: "string"}, + {Name: "mysql_version", Value: latestVersion, DataType: "string"}, // Always lock this test into the latest supported MySQL version + {Name: "service_credential_names", Value: string(serviceCredentialNamesJSON), DataType: "map(string)"}, + {Name: "existing_secrets_manager_instance_crn", Value: permanentResources["secretsManagerCRN"], DataType: "string"}, + {Name: "service_credential_secrets", Value: serviceCredentialSecrets, DataType: "list(object)"}, + {Name: "admin_pass_secrets_manager_secret_group", Value: fmt.Sprintf("mysql-%s-admin-secrets", options.Prefix), DataType: "string"}, + {Name: "admin_pass_secrets_manager_secret_name", Value: options.Prefix, DataType: "string"}, + {Name: "prefix", Value: options.Prefix, DataType: "string"}, + {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + {Name: "use_ibm_owned_encryption_key", Value: true, DataType: "bool"}, } - return nil + err = options.RunSchematicTest() + assert.Nil(t, err, "This should not have errored") } -func TestRunStandardSolutionSchematics(t *testing.T) { +// Test the security-enforced DA with defaults (KMS encryption enabled) +func TestRunSecurityEnforcedSolutionSchematics(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 := "mysql-st-da" + prefix := "mysqlse" options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ - Testing: t, - TarIncludePatterns: tarIncludePatterns, - TemplateFolder: standardSolutionTerraformDir, - BestRegionYAMLPath: regionSelectionPath, - Prefix: prefix, - ResourceGroup: resourceGroup, + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", securityEnforcedTerraformDir), + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), + fmt.Sprintf("%s/*.sh", "scripts"), + }, + TemplateFolder: securityEnforcedTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: prefix, + // ResourceGroup: resourceGroup, DeleteWorkspaceOnFail: false, CheckApplyResultForUpgrade: true, WaitJobCompleteMinutes: 60, }) + fmt.Print(options) - serviceCredentialSecrets := []map[string]interface{}{ + serviceCredentialSecrets := []map[string]any{ { "secret_group_name": fmt.Sprintf("%s-secret-group", options.Prefix), "service_credentials": []map[string]string{ @@ -172,43 +176,88 @@ func TestRunStandardSolutionSchematics(t *testing.T) { {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: "kms_endpoint_type", Value: "private", DataType: "string"}, + {Name: "existing_backup_kms_key_crn", Value: permanentResources["hpcs_south_root_key_crn"], DataType: "string"}, + {Name: "existing_resource_group_name", Value: resourceGroup, DataType: "string"}, {Name: "mysql_version", Value: "8.0", DataType: "string"}, // Always lock this test into the latest supported MySQL version - {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, + {Name: "service_credential_names", Value: string(serviceCredentialNamesJSON), 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_secrets_manager_secret_group", Value: fmt.Sprintf("mysql-%s-admin-secrets", options.Prefix), DataType: "string"}, + {Name: "admin_pass_secrets_manager_secret_name", Value: options.Prefix, DataType: "string"}, + {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + {Name: "prefix", Value: options.Prefix, DataType: "string"}, } err = options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") } -func TestRunStandardUpgradeSolution(t *testing.T) { +// Run upgrade test on security-enforced variation +func TestRunSecurityEnforcedUpgradeSolution(t *testing.T) { t.Parallel() - options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ - Testing: t, - TerraformDir: standardSolutionTerraformDir, + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", securityEnforcedTerraformDir), + fmt.Sprintf("%s/*.sh", "scripts"), + }, + TemplateFolder: securityEnforcedTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: "mysql-st-da-upg", - ResourceGroup: resourceGroup, + Prefix: "mysql-upg", + // ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, }) - options.TerraformVars = map[string]interface{}{ - "access_tags": permanentResources["accessTags"], - "existing_kms_instance_crn": permanentResources["hpcs_south_crn"], - "kms_endpoint_type": "public", - "provider_visibility": "public", - "resource_group_name": options.Prefix, + serviceCredentialSecrets := []map[string]any{ + { + "secret_group_name": fmt.Sprintf("%s-secret-group", options.Prefix), + "service_credentials": []map[string]string{ + { + "secret_name": fmt.Sprintf("%s-cred-reader", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Viewer", + }, + { + "secret_name": fmt.Sprintf("%s-cred-writer", options.Prefix), + "service_credentials_source_service_role_crn": "crn:v1:bluemix:public:iam::::role:Editor", + }, + }, + }, } - output, err := options.RunTestUpgrade() + 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: "prefix", Value: options.Prefix, DataType: "string"}, + {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: "existing_resource_group_name", Value: resourceGroup, DataType: "string"}, + {Name: "mysql_version", Value: "8.0", DataType: "string"}, // Always lock this test into the latest supported MySQL version + {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_secrets_manager_secret_name", Value: options.Prefix, DataType: "string"}, + {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + {Name: "admin_pass_secrets_manager_secret_group", Value: fmt.Sprintf("mysql-%s-admin-secrets", options.Prefix), DataType: "string"}, + } + + err = options.RunSchematicUpgradeTest() if !options.UpgradeTestSkipped { assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") } + } func TestRunUpgradeCompleteExample(t *testing.T) { @@ -252,32 +301,33 @@ func TestRunUpgradeCompleteExample(t *testing.T) { func TestPlanValidation(t *testing.T) { options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ - Testing: t, - TerraformDir: standardSolutionTerraformDir, - Prefix: "validate-plan", - ResourceGroup: resourceGroup, - Region: "us-south", // skip VPC region picker + Testing: t, + TerraformDir: fullyConfigurableSolutionTerraformDir, + Prefix: "val-plan", + // 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", - "mysql_version": "8.0", - "provider_visibility": "public", - "resource_group_name": options.Prefix, + "prefix": options.Prefix, + "region": "us-south", + "mysql_version": "8.0", + "provider_visibility": "public", + "existing_resource_group_name": resourceGroup, } // 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"], + "kms_encryption_enabled": true, } // Test the DA when using IBM owned encryption key var standardSolutionWithUseIbmOwnedEncKey = map[string]interface{}{ - "use_ibm_owned_encryption_key": true, + "kms_encryption_enabled": false, } // Create a map of the variables @@ -307,21 +357,17 @@ func TestPlanValidation(t *testing.T) { } } -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 -} - func TestRunExistingInstance(t *testing.T) { t.Parallel() prefix := fmt.Sprintf("mysql-t-%s", strings.ToLower(random.UniqueId())) realTerraformDir := ".." tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) - region := validICDRegions[mathrand.Intn(len(validICDRegions))] + + index, err := rand.Int(rand.Reader, big.NewInt(int64(len(validICDRegions)))) + if err != nil { + log.Fatalf("Failed to generate a secure random index: %v", err) + } + region := validICDRegions[index.Int64()] // Verify ibmcloud_api_key variable is set checkVariable := "TF_VAR_ibmcloud_api_key" @@ -332,7 +378,7 @@ func TestRunExistingInstance(t *testing.T) { logger.Log(t, "Tempdir: ", tempTerraformDir) existingTerraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: tempTerraformDir + "/examples/basic", - Vars: map[string]interface{}{ + Vars: map[string]any{ "prefix": prefix, "region": region, "mysql_version": latestVersion, @@ -353,14 +399,12 @@ func TestRunExistingInstance(t *testing.T) { Testing: t, TarIncludePatterns: []string{ "*.tf", - fmt.Sprintf("%s/*.tf", standardSolutionTerraformDir), - fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), - fmt.Sprintf("%s/*.tf", "modules/fscloud"), + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), fmt.Sprintf("%s/*.sh", "scripts"), }, - TemplateFolder: standardSolutionTerraformDir, + TemplateFolder: fullyConfigurableSolutionTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: "mysql-t-da", + Prefix: "mysqlex", ResourceGroup: resourceGroup, DeleteWorkspaceOnFail: false, WaitJobCompleteMinutes: 60, @@ -369,12 +413,10 @@ func TestRunExistingInstance(t *testing.T) { options.TerraformVars = []testschematic.TestSchematicTerraformVar{ {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, {Name: "existing_mysql_instance_crn", Value: terraform.Output(t, existingTerraformOptions, "mysql_crn"), DataType: "string"}, - {Name: "resource_group_name", Value: fmt.Sprintf("%s-resource-group", prefix), DataType: "string"}, - {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, {Name: "region", Value: region, DataType: "string"}, - {Name: "use_existing_resource_group", Value: true, DataType: "bool"}, + {Name: "existing_resource_group_name", Value: fmt.Sprintf("%s-resource-group", prefix), DataType: "string"}, {Name: "provider_visibility", Value: "public", DataType: "string"}, - {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + {Name: "prefix", Value: options.Prefix, DataType: "string"}, } err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") @@ -390,5 +432,13 @@ func TestRunExistingInstance(t *testing.T) { terraform.WorkspaceDelete(t, existingTerraformOptions, prefix) logger.Log(t, "END: Destroy (existing resources)") } +} +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 } diff --git a/variables.tf b/variables.tf index 58ff4fdf..a7e59210 100644 --- a/variables.tf +++ b/variables.tf @@ -46,7 +46,7 @@ variable "members" { type = number description = "Allocated number of members. Members can be scaled up but not down." default = 3 - # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. + # Validation is done in terraform plan phase by IBM provider, so no need to add any extra validation here } variable "cpu_count" { @@ -72,7 +72,7 @@ variable "member_host_flavor" { variable "memory_mb" { type = number - description = "Allocated memory per-member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" + description = "Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-mysql?topic=databases-for-mysql-resources-scaling)" default = 4096 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } @@ -113,7 +113,7 @@ variable "service_endpoints" { default = "private" validation { - condition = contains(["public", "private", "public-and-private"], var.service_endpoints) + 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'" } } @@ -264,7 +264,7 @@ variable "auto_scaling" { rate_units = optional(string, "mb") }) }) - 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://ibm.biz/autoscaling-considerations in the IBM Cloud Docs." + 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-mysql?topic=databases-for-mysql-autoscaling-mysql&interface=ui in the IBM Cloud Docs." default = null } @@ -276,6 +276,7 @@ variable "use_ibm_owned_encryption_key" { type = bool description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input." default = true + validation { condition = !( var.use_ibm_owned_encryption_key == true && @@ -306,7 +307,6 @@ variable "use_ibm_owned_encryption_key" { ) error_message = "When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'." } - } variable "use_default_backup_encryption_key" { @@ -353,7 +353,7 @@ variable "backup_encryption_key_crn" { variable "skip_iam_authorization_policy" { type = bool - description = "Set to true to skip the creation of an IAM authorization policy that permits all MySQL database instances in the resource group to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing_kms_instance_guid variable. In addition, no policy is created if var.kms_encryption_enabled is set to false." + description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for MySQL 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." default = false } diff --git a/version.tf b/version.tf index f98e22ca..857e2b1c 100644 --- a/version.tf +++ b/version.tf @@ -1,14 +1,14 @@ terraform { required_version = ">= 1.9.0" required_providers { - # Use "greater than or equal to" range in modules ibm = { - source = "IBM-Cloud/ibm" - version = ">= 1.79.2, < 2.0.0" + source = "IBM-Cloud/ibm" + # Use "greater than or equal to" range in modules + version = ">= 1.79.2, <2.0.0" } time = { source = "hashicorp/time" - version = ">= 0.9.1" + version = ">= 0.9.1, < 1.0.0" } } }