diff --git a/.catalog-onboard-pipeline.yaml b/.catalog-onboard-pipeline.yaml index a91030f9..4219da21 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: 0298facd-3e69-43fa-87c0-4d3d0b3c887e 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/.secrets.baseline b/.secrets.baseline index 92bb3fa1..af4c0c7c 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-07-25T15:38:43Z", + "generated_at": "2025-05-12T14:07:27Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -87,7 +87,7 @@ "verified_result": null } ], - "solutions/standard/DA-types.md": [ + "solutions/fully-configurable/DA-types.md": [ { "hashed_secret": "44cdfc3615970ada14420caaaa5c5745fca06002", "is_secret": false, diff --git a/README.md b/README.md index 221d8011..e072dd2e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ To attach access management tags to resources in this module, you need the follo |------|---------| | [terraform](#requirement\_terraform) | >= 1.9.0 | | [ibm](#requirement\_ibm) | >= 1.79.2, <2.0.0 | -| [time](#requirement\_time) | >= 0.9.1 | +| [time](#requirement\_time) | >= 0.9.1, < 1.0.0 | ### Modules @@ -83,7 +83,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.postgresql_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 | @@ -100,16 +100,16 @@ To attach access management tags to resources in this module, you need the follo | [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 | | [configuration](#input\_configuration) | Database configuration parameters, see https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-changing-configuration&interface=api for more details. |
object({
shared_buffers = optional(number)
max_connections = optional(number)
# below field gives error when sent to provider
# tracking issue: https://github.com/IBM-Cloud/terraform-provider-ibm/issues/5403
# max_locks_per_transaction = optional(number)
max_prepared_transactions = optional(number)
synchronous_commit = optional(string)
effective_io_concurrency = optional(number)
deadlock_timeout = optional(number)
log_connections = optional(string)
log_disconnections = optional(string)
log_min_duration_statement = optional(number)
tcp_keepalives_idle = optional(number)
tcp_keepalives_interval = optional(number)
tcp_keepalives_count = optional(number)
archive_timeout = optional(number)
wal_level = optional(string)
max_replication_slots = optional(number)
max_wal_senders = optional(number)
})
| `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-postgresql?topic=databases-for-postgresql-resources-scaling) | `number` | `0` | no | +| [disk\_mb](#input\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling) | `number` | `5120` | 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\_cpu\_count](#input\_member\_cpu\_count) | Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations | `number` | `0` | no | -| [member\_disk\_mb](#input\_member\_disk\_mb) | Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations | `number` | `5120` | 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). Ignored during restore and point in time recovery operations | `string` | `null` | no | -| [member\_memory\_mb](#input\_member\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations | `number` | `4096` | 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` | `2` | no | +| [memory\_mb](#input\_memory\_mb) | Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling) | `number` | `4096` | no | | [name](#input\_name) | The name to give the Postgresql instance. | `string` | n/a | yes | -| [pg\_version](#input\_pg\_version) | Version of the PostgreSQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | | [pitr\_id](#input\_pitr\_id) | (Optional) The ID of the source deployment PostgreSQL instance that you want to recover back to. The PostgreSQL instance is expected to be in an up and in running state. | `string` | `null` | no | | [pitr\_time](#input\_pitr\_time) | (Optional) The timestamp in UTC format (%Y-%m-%dT%H:%M:%SZ) for any time in the last 7 days that you want to restore to. If empty string ("") is passed, earliest\_point\_in\_time\_recovery\_time will be used as pitr\_time. To retrieve the timestamp, run the command (ibmcloud cdb postgresql earliest-pitr-timestamp ). For more info on Point-in-time Recovery, see https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-pitr | `string` | `null` | no | +| [postgresql\_version](#input\_postgresql\_version) | Version of the PostgreSQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used. | `string` | `null` | no | | [region](#input\_region) | The region where you want to deploy your 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-postgresql?topic=databases-for-postgresql-read-only-replicas | `string` | `null` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the PostgreSQL instance will be created. | `string` | n/a | yes | diff --git a/cra-config.yaml b/cra-config.yaml index 6ef1cc5c..785bbe3c 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" - CRA_IGNORE_RULES_FILE: "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: + - 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_prefix: "test-postgres-standard" - TF_VAR_resource_group_name: "test" + TF_VAR_existing_resource_group_name: "geretain-test-postgres" + TF_VAR_kms_encryption_enabled: true TF_VAR_provider_visibility: "public" + TF_VAR_prefix: "test" diff --git a/examples/backup/main.tf b/examples/backup/main.tf index 6ed536ed..737afd2f 100644 --- a/examples/backup/main.tf +++ b/examples/backup/main.tf @@ -15,7 +15,7 @@ module "postgresql_db" { source = "../.." resource_group_id = module.resource_group.resource_group_id name = "${var.prefix}-postgres" - pg_version = var.pg_version + postgresql_version = var.pg_version region = var.region tags = var.resource_tags access_tags = var.access_tags @@ -35,7 +35,7 @@ module "restored_icd_postgresql" { # version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release resource_group_id = module.resource_group.resource_group_id name = "${var.prefix}-postgres-restored" - pg_version = var.pg_version + postgresql_version = var.pg_version region = var.region tags = var.resource_tags access_tags = var.access_tags diff --git a/examples/basic/main.tf b/examples/basic/main.tf index 2cbaceec..cde9de5d 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -21,7 +21,7 @@ module "database" { # version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release resource_group_id = module.resource_group.resource_group_id name = "${var.prefix}-data-store" - pg_version = var.pg_version + postgresql_version = var.postgresql_version region = var.region tags = var.resource_tags access_tags = var.access_tags @@ -61,10 +61,10 @@ module "read_only_replica_postgresql_db" { region = var.region tags = var.resource_tags access_tags = var.access_tags - pg_version = var.pg_version + postgresql_version = var.postgresql_version remote_leader_crn = module.database.crn member_host_flavor = "multitenant" - member_memory_mb = 4096 # Must be an increment of 384 megabytes. The minimum size of a read-only replica is 2 GB RAM, new hosting model minimum is 4 GB RAM. - member_disk_mb = 5120 # Must be an increment of 512 megabytes. The minimum size of a read-only replica is 5 GB of disk + memory_mb = 4096 # Must be an increment of 384 megabytes. The minimum size of a read-only replica is 2 GB RAM, new hosting model minimum is 4 GB RAM. + disk_mb = 5120 # Must be an increment of 512 megabytes. The minimum size of a read-only replica is 5 GB of disk depends_on = [time_sleep.wait_time] } diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index 7029af4b..f40cfbd4 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -14,7 +14,7 @@ variable "prefix" { description = "Prefix to append to all resources created by this example" } -variable "pg_version" { +variable "postgresql_version" { description = "Version of the postgresql instance. If no value passed, the current ICD preferred version is used." type = string default = null diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 08ebdbd8..b7d942d8 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -106,12 +106,12 @@ module "icd_postgresql" { # remove the above line and uncomment the below 2 lines to consume the module from the registry # source = "terraform-ibm-modules/icd-postgresql/ibm" # version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release - resource_group_id = module.resource_group.resource_group_id - name = "${var.prefix}-postgres" - region = var.region - pg_version = var.pg_version - admin_pass = var.admin_pass - users = var.users + resource_group_id = module.resource_group.resource_group_id + name = "${var.prefix}-postgres" + region = var.region + postgresql_version = var.pg_version + admin_pass = var.admin_pass + users = var.users # Example of how to use different KMS keys for data and backups use_ibm_owned_encryption_key = false use_same_kms_key_for_backups = false diff --git a/examples/pitr/main.tf b/examples/pitr/main.tf index b3add4ff..0646e9a1 100644 --- a/examples/pitr/main.tf +++ b/examples/pitr/main.tf @@ -21,12 +21,12 @@ module "postgresql_db_pitr" { region = var.region tags = var.resource_tags access_tags = var.access_tags - member_memory_mb = 4096 - member_disk_mb = 5120 - member_cpu_count = 0 + memory_mb = 4096 + disk_mb = 5120 + cpu_count = 0 member_host_flavor = "multitenant" members = var.members - pg_version = var.pg_version + postgresql_version = var.pg_version pitr_id = var.pitr_id pitr_time = var.pitr_time } diff --git a/ibm_catalog.json b/ibm_catalog.json index 354000b6..a987b832 100644 --- a/ibm_catalog.json +++ b/ibm_catalog.json @@ -9,11 +9,11 @@ "target_terraform", "terraform", "data_management", + "database", "solution" ], "keywords": [ "postgresql", - "postgres", "IaC", "infrastructure as code", "terraform", @@ -23,35 +23,36 @@ "relational" ], "short_description": "Creates and configures an instance of IBM Cloud Databases for PostgreSQL.", - "long_description": "This architecture supports creating and configuring an instance of Databases for PostgreSQL with KMS encryption.", + "long_description": "This architecture supports creating and configuring an instance of [Databases for PostgreSQL](https://www.ibm.com/products/databases-for-postgresql), with optional KMS encryption.\n\nℹ️ This Terraform-based automation is part of a broader suite of IBM-maintained Infrastructure as Code (IaC) assets, 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-postgresql/blob/main/README.md", "offering_icon_url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/main/images/postgresql.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-postgresql/issues](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/issues). Please note this product is not supported via the IBM Cloud Support Center.", "features": [ { - "title": "Creates an instance of Databases for PostgreSQL", - "description": "Creates and configures an IBM Cloud Databases for PostgreSQL instance." + "title": "KMS encryption", + "description": "Provides [KMS encryption](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-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-postgresql?topic=databases-for-postgresql-autoscaling&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 PostgreSQL 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": [ @@ -64,9 +65,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": [ @@ -76,17 +78,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 PostgreSQL", - "description": "This architecture creates an instance of IBM Cloud Databases for PostgreSQL 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": [ @@ -106,6 +115,7 @@ }, { "key": "provider_visibility", + "hidden": true, "options": [ { "displayname": "private", @@ -122,22 +132,23 @@ ] }, { - "key": "use_existing_resource_group" - }, - { - "key": "resource_group_name" - }, - { - "key": "prefix", - "required": true + "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": "name" + "key": "prefix" }, { "key": "region", "required": true, - "default_value": "us-south", "options": [ { "displayname": "Chennai (che01)", @@ -190,14 +201,9 @@ ] }, { - "key": "pg_version", - "required": false, - "default_value": "__NULL__", + "key": "postgresql_version", + "required": true, "options": [ - { - "displayname": "preferred", - "value": "__NULL__" - }, { "displayname": "13", "value": "13" @@ -220,6 +226,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" }, @@ -236,38 +267,58 @@ "key": "member_host_flavor" }, { - "key": "configuration" + "key": "auto_scaling" }, { - "key": "service_credential_names" + "key": "configuration" }, { - "key": "admin_pass" + "key": "service_endpoints", + "options": [ + { + "displayname": "private", + "value": "private" + }, + { + "displayname": "public", + "value": "public" + }, + { + "displayname": "public-and-private", + "value": "public-and-private" + } + ] }, { - "key": "users" + "key": "service_credential_names" }, { - "key": "resource_tags" + "key": "admin_pass" }, { - "key": "access_tags" + "key": "users", + "type": "array", + "custom_config": { + "type": "textarea", + "grouping": "deployment", + "original_grouping": "deployment" + } }, { - "key": "use_ibm_owned_encryption_key" + "key": "ibmcloud_kms_api_key" }, { - "key": "ibmcloud_kms_api_key" + "key": "kms_encryption_enabled" }, { - "key": "existing_kms_instance_crn", - "required": true + "key": "existing_kms_instance_crn" }, { "key": "existing_kms_key_crn" }, { "key": "kms_endpoint_type", + "hidden": true, "options": [ { "displayname": "public", @@ -279,18 +330,12 @@ } ] }, - { - "key": "skip_pg_kms_auth_policy" - }, { "key": "key_ring_name" }, { "key": "key_name" }, - { - "key": "auto_scaling" - }, { "key": "backup_crn" }, @@ -300,6 +345,264 @@ { "key": "use_default_backup_encryption_key" }, + { + "key": "skip_postgresql_kms_auth_policy" + }, + { + "key": "existing_postgresql_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-postgresql" + }, + { + "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 PostgreSQL instance on IBM Cloud", + "url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/main/reference-architecture/deployable-architecture-postgresql.svg", + "type": "image/svg+xml" + }, + "description": "This architecture supports creating and configuring an instance of Databases for PostgreSQL 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": "Chennai (che01)", + "value": "che01" + }, + { + "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": "postgresql_version", + "required": true, + "options": [ + { + "displayname": "13", + "value": "13" + }, + { + "displayname": "14", + "value": "14" + }, + { + "displayname": "15", + "value": "15" + }, + { + "displayname": "16", + "value": "16" + }, + { + "displayname": "17", + "value": "17" + } + ] + }, + { + "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" + }, + { + "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": "admin_pass" + }, +{ + "key": "skip_postgresql_kms_auth_policy" + }, + { + "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": "remote_leader_crn" }, diff --git a/main.tf b/main.tf index 2c50ccfa..a8249a51 100644 --- a/main.tf +++ b/main.tf @@ -1,13 +1,9 @@ -############################################################################## -# ICD PostgreSQL module - -# Input variable validation -# (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) -# -# TODO: Replace with terraform cross variable validation: https://github.ibm.com/GoldenEye/issues/issues/10836 +######################################################################################################################## +# 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'. @@ -167,22 +163,21 @@ resource "time_sleep" "wait_for_backup_kms_authorization_policy" { # Create postgresql database resource "ibm_database" "postgresql_db" { - depends_on = [time_sleep.wait_for_authorization_policy] - resource_group_id = var.resource_group_id - name = var.name - service = "databases-for-postgresql" - location = var.region - plan = "standard" # Only standard plan is available for postgres - backup_id = var.backup_crn - remote_leader_id = var.remote_leader_crn - version = var.pg_version - tags = var.tags - adminpassword = var.admin_pass - service_endpoints = var.service_endpoints - # 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 + depends_on = [time_sleep.wait_for_authorization_policy] + name = var.name + plan = "standard" # Only standard plan is available for postgres + location = var.region + service = "databases-for-postgresql" + version = var.postgresql_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 + configuration = var.configuration != null ? jsonencode({ for k, v in var.configuration : k => v if v != null }) : null point_in_time_recovery_deployment_id = var.pitr_id point_in_time_recovery_time = var.pitr_time @@ -211,7 +206,7 @@ resource "ibm_database" "postgresql_db" { id = var.member_host_flavor } disk { - allocation_mb = var.member_disk_mb + allocation_mb = var.disk_mb } dynamic "members" { for_each = var.remote_leader_crn == null ? [1] : [] @@ -231,13 +226,13 @@ resource "ibm_database" "postgresql_db" { id = var.member_host_flavor } disk { - allocation_mb = var.member_disk_mb + allocation_mb = var.disk_mb } memory { - allocation_mb = var.member_memory_mb + allocation_mb = var.memory_mb } cpu { - allocation_count = var.member_cpu_count + allocation_count = var.cpu_count } dynamic "members" { for_each = var.remote_leader_crn == null ? [1] : [] @@ -254,13 +249,13 @@ resource "ibm_database" "postgresql_db" { content { group_id = "member" # Only member type is allowed for IBM Cloud Databases memory { - allocation_mb = var.member_memory_mb + allocation_mb = var.memory_mb } disk { - allocation_mb = var.member_disk_mb + allocation_mb = var.disk_mb } cpu { - allocation_count = var.member_cpu_count + allocation_count = var.cpu_count } dynamic "members" { for_each = var.remote_leader_crn == null ? [1] : [] @@ -315,7 +310,7 @@ resource "ibm_database" "postgresql_db" { } } -resource "ibm_resource_tag" "postgresql_tag" { +resource "ibm_resource_tag" "access_tag" { count = length(var.access_tags) == 0 ? 0 : 1 resource_id = ibm_database.postgresql_db.resource_crn tags = var.access_tags @@ -325,6 +320,7 @@ resource "ibm_resource_tag" "postgresql_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" @@ -351,6 +347,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/main.tf b/modules/fscloud/main.tf index 15403a85..fc3ef21f 100644 --- a/modules/fscloud/main.tf +++ b/modules/fscloud/main.tf @@ -6,7 +6,7 @@ module "postgresql_db" { remote_leader_crn = var.remote_leader_crn skip_iam_authorization_policy = var.skip_iam_authorization_policy service_endpoints = "private" - pg_version = var.pg_version + postgresql_version = var.pg_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 @@ -17,9 +17,9 @@ module "postgresql_db" { access_tags = var.access_tags cbr_rules = var.cbr_rules configuration = var.configuration - member_memory_mb = var.member_memory_mb - member_disk_mb = var.member_disk_mb - member_cpu_count = var.member_cpu_count + memory_mb = var.member_memory_mb + disk_mb = var.member_disk_mb + cpu_count = var.member_cpu_count member_host_flavor = var.member_host_flavor members = var.members admin_pass = var.admin_pass diff --git a/outputs.tf b/outputs.tf index a314d105..29eff954 100644 --- a/outputs.tf +++ b/outputs.tf @@ -7,16 +7,16 @@ output "id" { value = ibm_database.postgresql_db.id } -output "guid" { - description = "Postgresql instance guid" - value = ibm_database.postgresql_db.guid -} - output "version" { description = "Postgresql instance version" value = ibm_database.postgresql_db.version } +output "guid" { + description = "Postgresql instance guid" + value = ibm_database.postgresql_db.guid +} + output "crn" { description = "Postgresql instance crn" value = ibm_database.postgresql_db.resource_crn diff --git a/reference-architecture/deployable-architecture-postgresql.svg b/reference-architecture/deployable-architecture-postgresql.svg index f35448c6..b9cd650e 100644 --- a/reference-architecture/deployable-architecture-postgresql.svg +++ b/reference-architecture/deployable-architecture-postgresql.svg @@ -1,4 +1,4 @@ - + -
IBM Cloud
IBM Cloud
KMS Encryption
KMS Encryption
Region
Region
Resource Group
Resource Group
IBM Cloud PostgreSQL Instance
IBM Cloud PostgreSQL Instance
Text is not SVG - cannot display
\ No newline at end of file +IBM CloudRegionResource GroupDatabase for PostgreSQL
Pg
Pg
[Optional] KMS
[Optional] KMS
Key Ring
Key Ring
postgresql-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..1adfb862 --- /dev/null +++ b/solutions/fully-configurable/README.md @@ -0,0 +1,3 @@ +# Cloud automation for PostgreSQL (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..85531edd --- /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": $PREFIX, + "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 74% rename from solutions/standard/main.tf rename to solutions/fully-configurable/main.tf index d8e067bb..7b54ba91 100644 --- a/solutions/standard/main.tf +++ b/solutions/fully-configurable/main.tf @@ -1,29 +1,29 @@ ####################################################################################################################### # 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.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 } -####################################################################################################################### -# KMS related variable validation -# (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) -# -# TODO: Replace with terraform cross variable validation: https://github.ibm.com/GoldenEye/issues/issues/10836 -####################################################################################################################### - ####################################################################################################################### # KMS encryption key ####################################################################################################################### locals { - create_new_kms_key = var.existing_postgresql_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 - postgres_key_name = (var.prefix != null && var.prefix != "") ? "${var.prefix}-${var.key_name}" : var.key_name - postgres_key_ring_name = (var.prefix != null && var.prefix != "") ? "${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_postgresql_instance_crn == null && + var.existing_kms_key_crn == null + ) + postgres_key_name = "${local.prefix}${var.key_name}" + postgres_key_ring_name = "${local.prefix}${var.key_ring_name}" } module "kms" { @@ -48,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. } ] } @@ -92,24 +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_postgresql_instance_crn == null && !var.skip_pg_kms_auth_policy && var.ibmcloud_kms_api_key != null && !var.use_ibm_owned_encryption_key - create_cross_account_backup_kms_auth_policy = var.existing_postgresql_instance_crn == null && !var.skip_pg_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_postgresql_kms_auth_policy && var.ibmcloud_kms_api_key != null + create_cross_account_backup_kms_auth_policy = var.kms_encryption_enabled && !var.skip_postgresql_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_postgresql_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_postgresql_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_postgresql_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_postgresql_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.postgres_key_ring_name, local.postgres_key_name)].crn - kms_key_id = var.existing_postgresql_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.postgres_key_ring_name, local.postgres_key_name)].key_id - kms_region = var.existing_postgresql_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 PostgreSQL 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_postgresql_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_postgresql_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_postgresql_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_postgresql_instance_crn != null ? null : var.existing_kms_key_crn != null ? var.existing_kms_key_crn : module.kms[0].keys[format("%s.%s", local.postgres_key_ring_name, local.postgres_key_name)].crn + kms_key_id = !var.kms_encryption_enabled || var.existing_postgresql_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.postgres_key_ring_name, local.postgres_key_name)].key_id + kms_region = !var.kms_encryption_enabled || var.existing_postgresql_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_postgresql_instance_crn != null || var.use_ibm_owned_encryption_key ? null : var.existing_backup_kms_key_crn + backup_kms_key_crn = var.existing_postgresql_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 } @@ -227,8 +226,9 @@ locals { # if - replace first char with J # elseif _ replace first char with K # else use asis + generated_admin_password = (length(random_password.admin_password) > 0 ? (startswith(random_password.admin_password[0].result, "-") ? "J${substr(random_password.admin_password[0].result, 1, -1)}" : startswith(random_password.admin_password[0].result, "_") ? "K${substr(random_password.admin_password[0].result, 1, -1)}" : random_password.admin_password[0].result) : null) # admin password to use - admin_pass = var.admin_pass == null ? (startswith(random_password.admin_password[0].result, "-") ? "J${substr(random_password.admin_password[0].result, 1, -1)}" : startswith(random_password.admin_password[0].result, "_") ? "K${substr(random_password.admin_password[0].result, 1, -1)}" : random_password.admin_password[0].result) : var.admin_pass + admin_pass = var.admin_pass == null ? local.generated_admin_password : var.admin_pass } ####################################################################################################################### @@ -276,15 +276,15 @@ data "ibm_database_connection" "existing_connection" { # Create new instance module "postgresql_db" { count = var.existing_postgresql_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.prefix}-${var.name}" : var.name + name = "${local.prefix}${var.name}" region = var.region remote_leader_crn = var.remote_leader_crn - skip_iam_authorization_policy = var.skip_pg_kms_auth_policy - pg_version = var.pg_version - use_ibm_owned_encryption_key = var.use_ibm_owned_encryption_key + postgresql_version = var.postgresql_version + skip_iam_authorization_policy = var.skip_postgresql_kms_auth_policy + 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 @@ -296,13 +296,14 @@ module "postgresql_db" { users = var.users members = var.members member_host_flavor = var.member_host_flavor - member_memory_mb = var.member_memory_mb - member_disk_mb = var.member_disk_mb - member_cpu_count = var.member_cpu_count + memory_mb = var.member_memory_mb + disk_mb = var.member_disk_mb + cpu_count = var.member_cpu_count auto_scaling = var.auto_scaling configuration = var.configuration service_credential_names = var.service_credential_names backup_crn = var.backup_crn + service_endpoints = var.service_endpoints } locals { diff --git a/solutions/standard/outputs.tf b/solutions/fully-configurable/outputs.tf similarity index 95% rename from solutions/standard/outputs.tf rename to solutions/fully-configurable/outputs.tf index a0f14cbd..f320e271 100644 --- a/solutions/standard/outputs.tf +++ b/solutions/fully-configurable/outputs.tf @@ -7,16 +7,16 @@ output "id" { value = local.postgresql_id } -output "guid" { - description = "PostgreSQL instance guid" - value = local.postgresql_guid -} - output "version" { description = "PostgreSQL instance version" value = local.postgresql_version } +output "guid" { + description = "PostgreSQL instance guid" + value = local.postgresql_guid +} + output "crn" { description = "PostgreSQL instance crn" value = local.postgresql_crn @@ -30,7 +30,7 @@ output "service_credentials_json" { output "service_credentials_object" { description = "Service credentials object" - value = var.existing_postgresql_instance_crn != null ? null : module.postgresql_db[0].service_credentials_json + value = var.existing_postgresql_instance_crn != null ? null : module.postgresql_db[0].service_credentials_object sensitive = true } 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 61% rename from solutions/standard/variables.tf rename to solutions/fully-configurable/variables.tf index c248cda5..0850bef1 100644 --- a/solutions/standard/variables.tf +++ b/solutions/fully-configurable/variables.tf @@ -8,37 +8,43 @@ variable "ibmcloud_api_key" { sensitive = true } -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 - default = "private" - - validation { - condition = contains(["public", "private", "public-and-private"], var.provider_visibility) - error_message = "Invalid visibility option. Allowed values are 'public', 'private', or 'public-and-private'." - } -} - -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 PostgreSQL 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. To not use any prefix value, you can set this value to `null` or an empty string." - default = "dev" + 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 PostgreSQL instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + description = "The name of the Databases for PostgreSQL instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." default = "postgresql" } @@ -53,24 +59,16 @@ variable "region" { } } -variable "pg_version" { - description = "The version of the PostgreSQL instance. If no value is specified, the current preferred version of PostgreSQL is used." +variable "existing_postgresql_instance_crn" { type = string default = null + description = "The CRN of an existing Databases for Postgresql instance. If no value is specified, a new instance is created." } -variable "backup_crn" { +variable "postgresql_version" { + description = "The version of the PostgreSQL instance. If no value is specified, the current preferred version of PostgreSQL is used." 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:'" - } } variable "remote_leader_crn" { @@ -79,19 +77,31 @@ variable "remote_leader_crn" { default = null } -variable "existing_postgresql_instance_crn" { - type = string - default = null - description = "The CRN of an existing Databases for Postgresql instance. If no value is specified, a new instance is created." -} - ############################################################################## # 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-postgresql?topic=databases-for-postgresql-resources-scaling)." - default = 3 + default = 2 +} + +variable "member_memory_mb" { + type = number + description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling)" + default = 4096 } variable "member_cpu_count" { @@ -117,51 +127,8 @@ variable "member_host_flavor" { } } -variable "member_memory_mb" { - type = number - description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling)" - default = 4096 -} - -variable "admin_pass" { - type = string - description = "The password for the database administrator. If the admin password is null, 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. 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 PostgreSQL instance. This blocks creates native postgres database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/standard/DA-types.md)." -} - -variable "service_credential_names" { - description = "The map of name and role for service credentials that you want to create for the database. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/standard/DA-types.md)." - type = map(string) - default = {} -} - -variable "resource_tags" { - type = list(string) - description = "Optional list of tags to be added to the PostgreSQL instance." - default = [] -} - -variable "access_tags" { - type = list(string) - description = "A list of access tags to apply to the PostgreSQL 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 PostgreSQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/standard/DA-types.md)" + description = "Database Configuration for PostgreSQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/fully-configurable/DA-types.md)" type = object({ shared_buffers = optional(number) max_connections = optional(number) @@ -199,92 +166,101 @@ variable "configuration" { } } -############################################################## -# 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 = "The 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-postgresql/tree/main/solutions/standard/DA-types.md)" +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-postgresql/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 the admin password is null, 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. 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 PostgreSQL instance. This blocks creates native postgres database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/fully-configurable/DA-types.md)." +} + +variable "resource_tags" { + type = list(string) + description = "Optional list of tags to be added to the PostgreSQL instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the PostgreSQL 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 - # this validation ensures IBM-owned key is not used when KMS details are provided validation { - condition = ( + condition = (!var.kms_encryption_enabled || var.existing_postgresql_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 Postgresql instance is given validation { - condition = !( - var.existing_postgresql_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 } -variable "existing_backup_kms_key_crn" { +variable "kms_endpoint_type" { 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)." - default = null + description = "The type of endpoint to use for communicating with the Key Protect or Hyper Protect Crypto Services instance. Possible values: `public`, `private`. Applies only if `existing_kms_key_crn` is not specified." + default = "private" + validation { + condition = can(regex("public|private", var.kms_endpoint_type)) + error_message = "The kms_endpoint_type value must be 'public' or 'private'." + } } -variable "key_name" { +variable "skip_postgresql_kms_auth_policy" { + type = bool + description = "Whether to create an IAM authorization policy that permits all Databases for PostgreSQL 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 - default = "postgresql-key" - description = "The name for the key created for the PostgreSQL key. Applies only if not specifying an existing key or using IBM owned keys. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + 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 PostgreSQL instance. Leave this input empty if the same account owns both instances." + sensitive = true + default = null } variable "key_ring_name" { @@ -293,31 +269,76 @@ variable "key_ring_name" { description = "The name for the key ring created for the PostgreSQL key. Applies only if not specifying an existing key or using IBM owned keys. If a prefix input variable is specified, the prefix is added to the name in the `-` format." } +variable "key_name" { + type = string + default = "postgresql-key" + description = "The name for the key created for the Databases for Redis 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. 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 } -variable "kms_endpoint_type" { +variable "backup_crn" { type = string - description = "The type of endpoint to use for communicating with the Key Protect or Hyper Protect Crypto Services instance. Possible values: `public`, `private`." - default = "private" + 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 = can(regex("public|private", var.kms_endpoint_type)) - error_message = "The kms_endpoint_type value must be 'public' or 'private'." + 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:'" } } -variable "skip_pg_kms_auth_policy" { - type = bool - description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for PostgreSQL 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 `use_ibm_owned_encryption_key` is true." - default = false +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 + default = "private" + + validation { + condition = contains(["public", "private", "public-and-private"], var.provider_visibility) + error_message = "Invalid visibility option. Allowed values are 'public', 'private', or 'public-and-private'." + } } -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 PostgreSQL instance. Leave this input empty if the same account owns both instances." - sensitive = true +############################################################## +# 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-postgresql/blob/main/solutions/fully-configurable/DA-types.md#autoscaling)" default = null } diff --git a/solutions/standard/version.tf b/solutions/fully-configurable/version.tf similarity index 99% rename from solutions/standard/version.tf rename to solutions/fully-configurable/version.tf index 4acdd237..3983f64a 100644 --- a/solutions/standard/version.tf +++ b/solutions/fully-configurable/version.tf @@ -1,6 +1,5 @@ terraform { required_version = ">= 1.9.0" - # Lock DA into an exact provider version - renovate automation will keep it updated required_providers { ibm = { diff --git a/solutions/security-enforced/README.md b/solutions/security-enforced/README.md new file mode 100644 index 00000000..a2ced959 --- /dev/null +++ b/solutions/security-enforced/README.md @@ -0,0 +1,3 @@ +# Cloud automation for PostgreSQL (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 79% rename from solutions/standard/catalogValidationValues.json.template rename to solutions/security-enforced/catalogValidationValues.json.template index 964f665e..5d462e78 100644 --- a/solutions/standard/catalogValidationValues.json.template +++ b/solutions/security-enforced/catalogValidationValues.json.template @@ -3,6 +3,6 @@ "region": "us-south", "resource_tags": $TAGS, "name": $PREFIX, - "resource_group_name": $PREFIX, + "existing_resource_group_name": $PREFIX, "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..c20e6fa2 --- /dev/null +++ b/solutions/security-enforced/main.tf @@ -0,0 +1,36 @@ +module "postgresql_db" { + 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_postgresql_instance_crn = var.existing_postgresql_instance_crn + postgresql_version = var.postgresql_version + remote_leader_crn = var.remote_leader_crn + 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_postgresql_kms_auth_policy = var.skip_postgresql_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 + service_endpoints = "private" +} diff --git a/solutions/security-enforced/outputs.tf b/solutions/security-enforced/outputs.tf new file mode 100644 index 00000000..f759ad5d --- /dev/null +++ b/solutions/security-enforced/outputs.tf @@ -0,0 +1,45 @@ +############################################################################## +# Outputs +############################################################################## + +output "id" { + description = "PostgreSQL instance id" + value = module.postgresql_db.id +} + +output "version" { + description = "PostgreSQL instance version" + value = module.postgresql_db.version +} + +output "guid" { + description = "PostgreSQL instance guid" + value = module.postgresql_db.guid +} + +output "crn" { + description = "PostgreSQL instance crn" + value = module.postgresql_db.crn +} + +output "service_credentials_json" { + description = "Service credentials json map" + value = module.postgresql_db.service_credentials_json + sensitive = true +} + +output "service_credentials_object" { + description = "Service credentials object" + value = module.postgresql_db.service_credentials_object + sensitive = true +} + +output "hostname" { + description = "PostgreSQL instance hostname" + value = module.postgresql_db.hostname +} + +output "port" { + description = "PostgreSQL instance port" + value = module.postgresql_db.port +} 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..b74bfd03 --- /dev/null +++ b/solutions/security-enforced/variables.tf @@ -0,0 +1,289 @@ +############################################################################## +# 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 PostgreSQL instance. If a prefix input variable is specified, the prefix is added to the name in the `-` format." + default = "postgresql" +} + +variable "region" { + description = "The region where you want to deploy your instance." + type = string + default = "us-south" +} + +variable "existing_postgresql_instance_crn" { + type = string + default = null + description = "The CRN of an existing Databases for Postgresql instance. If no value is specified, a new instance is created." +} + +variable "postgresql_version" { + description = "The version of the Databases for Redis 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-postgresql?topic=databases-for-postgresql-read-only-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-postgresql?topic=databases-for-postgresql-resources-scaling)." + default = 2 +} + +variable "member_memory_mb" { + type = number + description = "The memory per member that is allocated. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-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-postgresql?topic=databases-for-postgresql-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-postgresql?topic=databases-for-postgresql-resources-scaling)." + default = 5120 +} + +variable "member_host_flavor" { + type = string + description = "The host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." + default = "multitenant" + # Prevent null or "", require multitenant or a machine type + validation { + condition = (length(var.member_host_flavor) > 0) + error_message = "Member host flavor must be specified. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." + } +} + +variable "configuration" { + description = "Database Configuration for PostgreSQL instance. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/main/solutions/fully-configurable/DA-types.md)" + type = object({ + shared_buffers = optional(number) + max_connections = optional(number) + max_prepared_transactions = optional(number) + synchronous_commit = optional(string) + effective_io_concurrency = optional(number) + deadlock_timeout = optional(number) + log_connections = optional(string) + log_disconnections = optional(string) + log_min_duration_statement = optional(number) + tcp_keepalives_idle = optional(number) + tcp_keepalives_interval = optional(number) + tcp_keepalives_count = optional(number) + archive_timeout = optional(number) + wal_level = optional(string) + max_replication_slots = optional(number) + max_wal_senders = optional(number) + }) + default = { + shared_buffers = 32000 + max_connections = 115 + max_prepared_transactions = 0 + synchronous_commit = "local" + effective_io_concurrency = 12 + deadlock_timeout = 10000 + log_connections = "on" + log_disconnections = "on" + log_min_duration_statement = 100 + tcp_keepalives_idle = 111 + tcp_keepalives_interval = 15 + tcp_keepalives_count = 6 + archive_timeout = 1800 + max_replication_slots = 10 + max_wal_senders = 12 + } +} + +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-postgresql/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. 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 PostgreSQL instance. This blocks creates native postgres database users. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/tree/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 PostgreSQL instance." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "A list of access tags to apply to the Databases for PostgreSQL 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_postgresql_kms_auth_policy" { + type = bool + description = "Whether to create an IAM authorization policy that permits all Databases for PostgreSQL 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 PostgreSQL instance. Leave this input empty if the same account owns both instances." + sensitive = true + default = null +} + +variable "key_ring_name" { + type = string + default = "postgresql-key-ring" + description = "The name for the key ring created for the Databases for PostgreSQL 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 = "postgresql-key" + description = "The name for the key created for the Databases for PostgreSQL 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-postgresql/tree/main/solutions/fully-configurable/DA-types.md)" + default = null +} diff --git a/solutions/security-enforced/version.tf b/solutions/security-enforced/version.tf new file mode 100644 index 00000000..2d5c091d --- /dev/null +++ b/solutions/security-enforced/version.tf @@ -0,0 +1,5 @@ +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 7dffac05..00000000 --- a/solutions/standard/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# IBM Cloud Databases for PostgreSQL - -This architecture creates an instance of IBM Cloud Databases for PostgreSQL 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 PostgreSQL instance with KMS encryption. -- Autoscaling rules for the database instance, if provided. - -![fscloud-postgresql](../../reference-architecture/deployable-architecture-postgresql.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 4b9aafc0..00000000 --- a/solutions/standard/moved.tf +++ /dev/null @@ -1,4 +0,0 @@ -moved { - from = module.postgresql_db - to = module.postgresql_db[0] -} diff --git a/solutions/standard/provider.tf b/solutions/standard/provider.tf deleted file mode 100644 index 8e9cf064..00000000 --- a/solutions/standard/provider.tf +++ /dev/null @@ -1,13 +0,0 @@ -provider "ibm" { - ibmcloud_api_key = var.ibmcloud_api_key - region = var.region - ibmcloud_timeout = 60 - visibility = var.provider_visibility -} - -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 -} diff --git a/tests/pr_test.go b/tests/pr_test.go index ee6dde0e..5b7eaabc 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -23,7 +23,8 @@ import ( "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) -const standardSolutionTerraformDir = "solutions/standard" +const fullyConfigurableSolutionTerraformDir = "solutions/fully-configurable" +const securityEnforcedSolutionTerraformDir = "solutions/security-enforced" const fscloudExampleTerraformDir = "examples/fscloud" const latestVersion = "16" @@ -57,58 +58,94 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestRunStandardSolutionSchematics(t *testing.T) { +func TestRunFullyConfigurableSolutionSchematics(t *testing.T) { t.Parallel() options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ Testing: t, TarIncludePatterns: []string{ "*.tf", - fmt.Sprintf("%s/*.tf", standardSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), fmt.Sprintf("%s/*.tf", "modules/fscloud"), fmt.Sprintf("%s/*.sh", "scripts"), }, - TemplateFolder: standardSolutionTerraformDir, + TemplateFolder: fullyConfigurableSolutionTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: "postgresql-st-da", + Prefix: "pg-fg-da", ResourceGroup: resourceGroup, DeleteWorkspaceOnFail: false, WaitJobCompleteMinutes: 60, }) options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "prefix", Value: options.Prefix, DataType: "string"}, {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: "kms_encryption_enabled", Value: true, DataType: "bool"}, {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, {Name: "existing_backup_kms_key_crn", Value: permanentResources["hpcs_south_root_key_crn"], DataType: "string"}, {Name: "kms_endpoint_type", Value: "private", DataType: "string"}, - {Name: "pg_version", Value: "16", DataType: "string"}, // Always lock this test into the latest supported PostgresSQL version - {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, + {Name: "postgresql_version", Value: "16", DataType: "string"}, // Always lock this test into the latest supported PostgresSQL version + {Name: "existing_resource_group_name", Value: resourceGroup, DataType: "string"}, {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, } err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") } -func TestRunStandardUpgradeSolution(t *testing.T) { +func TestRunSecurityEnforcedSolutionSchematics(t *testing.T) { + t.Parallel() + + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: []string{ + "*.tf", + fmt.Sprintf("%s/*.tf", securityEnforcedSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), + fmt.Sprintf("%s/*.tf", "modules/fscloud"), + fmt.Sprintf("%s/*.sh", "scripts"), + }, + TemplateFolder: securityEnforcedSolutionTerraformDir, + BestRegionYAMLPath: regionSelectionPath, + Prefix: "pg-se-da", + ResourceGroup: resourceGroup, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, + }) + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "prefix", Value: options.Prefix, DataType: "string", Secure: true}, + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "access_tags", Value: permanentResources["accessTags"], DataType: "list(string)"}, + {Name: "existing_kms_instance_crn", Value: permanentResources["hpcs_south_crn"], DataType: "string"}, + {Name: "existing_backup_kms_key_crn", Value: permanentResources["hpcs_south_root_key_crn"], DataType: "string"}, + {Name: "postgresql_version", Value: "16", DataType: "string"}, // Always lock this test into the latest supported PostgresSQL version + {Name: "existing_resource_group_name", Value: "geretain-test-postgres-security-enforced", DataType: "string"}, + {Name: "admin_pass", Value: GetRandomAdminPassword(t), DataType: "string"}, + } + err := options.RunSchematicTest() + assert.Nil(t, err, "This should not have errored") +} + +func TestRunSecurityEnforcedUpgradeSolution(t *testing.T) { t.Parallel() options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ Testing: t, - TerraformDir: standardSolutionTerraformDir, + TerraformDir: fullyConfigurableSolutionTerraformDir, Region: "us-south", - Prefix: "postgres-st-da-upg", + Prefix: "pg-fc-upg", ResourceGroup: resourceGroup, CheckApplyResultForUpgrade: true, }) 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, + "prefix": options.Prefix, + "access_tags": permanentResources["accessTags"], + "existing_kms_instance_crn": permanentResources["hpcs_south_crn"], + "existing_resource_group_name": resourceGroup, } output, err := options.RunTestUpgrade() @@ -121,8 +158,8 @@ func TestRunStandardUpgradeSolution(t *testing.T) { func TestPlanValidation(t *testing.T) { options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ Testing: t, - TerraformDir: standardSolutionTerraformDir, - Prefix: "validate-plan", + TerraformDir: fullyConfigurableSolutionTerraformDir, + Prefix: "val-plan", ResourceGroup: resourceGroup, Region: "us-south", // skip VPC region picker }) @@ -130,31 +167,31 @@ func TestPlanValidation(t *testing.T) { options.TerraformOptions.NoColor = true options.TerraformOptions.Logger = logger.Discard options.TerraformOptions.Vars = map[string]interface{}{ - "prefix": options.Prefix, - "region": "us-south", - "kms_endpoint_type": "public", - "pg_version": "16", - "provider_visibility": "public", - "resource_group_name": "validate-plan", + "prefix": options.Prefix, + "region": "us-south", + "kms_endpoint_type": "public", + "postgresql_version": "16", + "provider_visibility": "public", + "existing_resource_group_name": resourceGroup, } // Test the DA when using IBM owned encryption keys - var ibmOwnedEncrytionKeyTFVars = map[string]interface{}{ + var ibmOwnedEncryptionKeyTFVars = map[string]interface{}{ "use_default_backup_encryption_key": false, - "use_ibm_owned_encryption_key": true, + "kms_encryption_enabled": false, } // Test the DA when using Default Backup Encryption Key and not IBM owned encryption keys - var notIbmOwnedEncrytionKeyTFVars = map[string]interface{}{ + var notIbmOwnedEncryptionKeyTFVars = map[string]interface{}{ "existing_kms_instance_crn": permanentResources["hpcs_south_crn"], "use_default_backup_encryption_key": true, - "use_ibm_owned_encryption_key": false, + "kms_encryption_enabled": true, } // Create a map of the variables tfVarsMap := map[string]map[string]interface{}{ - "ibmOwnedEncrytionKeyTFVars": ibmOwnedEncrytionKeyTFVars, - "notIbmOwnedEncrytionKeyTFVars": notIbmOwnedEncrytionKeyTFVars, + "ibmOwnedEncryptionKeyTFVars": ibmOwnedEncryptionKeyTFVars, + "notIbmOwnedEncryptionKeyTFVars": notIbmOwnedEncryptionKeyTFVars, } _, initErr := terraform.InitE(t, options.TerraformOptions) @@ -191,7 +228,7 @@ func GetRandomAdminPassword(t *testing.T) string { func TestRunExistingInstance(t *testing.T) { t.Parallel() - prefix := fmt.Sprintf("postgresql-t-%s", strings.ToLower(random.UniqueId())) + prefix := fmt.Sprintf("pg-t-%s", strings.ToLower(random.UniqueId())) realTerraformDir := ".." tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) rand, err := rand.Int(rand.Reader, big.NewInt(int64(len(validICDRegions)))) @@ -211,10 +248,10 @@ func TestRunExistingInstance(t *testing.T) { existingTerraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: tempTerraformDir + "/examples/basic", Vars: map[string]interface{}{ - "prefix": prefix, - "region": region, - "pg_version": latestVersion, - "service_endpoints": "public-and-private", + "prefix": prefix, + "region": region, + "postgresql_version": latestVersion, + "service_endpoints": "public-and-private", }, // Set Upgrade to true to ensure latest version of providers and modules are used by terratest. // This is the same as setting the -upgrade=true flag with terraform. @@ -231,25 +268,25 @@ func TestRunExistingInstance(t *testing.T) { Testing: t, TarIncludePatterns: []string{ "*.tf", - fmt.Sprintf("%s/*.tf", standardSolutionTerraformDir), + fmt.Sprintf("%s/*.tf", fullyConfigurableSolutionTerraformDir), fmt.Sprintf("%s/*.tf", fscloudExampleTerraformDir), fmt.Sprintf("%s/*.tf", "modules/fscloud"), fmt.Sprintf("%s/*.sh", "scripts"), }, - TemplateFolder: standardSolutionTerraformDir, + TemplateFolder: fullyConfigurableSolutionTerraformDir, BestRegionYAMLPath: regionSelectionPath, - Prefix: "postgresql-da", + Prefix: "pg-da", ResourceGroup: resourceGroup, DeleteWorkspaceOnFail: false, WaitJobCompleteMinutes: 60, }) options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "prefix", Value: options.Prefix, DataType: "string"}, {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, {Name: "existing_postgresql_instance_crn", Value: terraform.Output(t, existingTerraformOptions, "postgresql_crn"), DataType: "string"}, - {Name: "resource_group_name", Value: fmt.Sprintf("%s-resource-group", prefix), DataType: "string"}, + {Name: "existing_resource_group_name", Value: resourceGroup, DataType: "string"}, {Name: "region", Value: region, DataType: "string"}, - {Name: "use_existing_resource_group", Value: true, DataType: "bool"}, {Name: "provider_visibility", Value: "public", DataType: "string"}, } err := options.RunSchematicTest() @@ -266,5 +303,4 @@ func TestRunExistingInstance(t *testing.T) { terraform.WorkspaceDelete(t, existingTerraformOptions, prefix) logger.Log(t, "END: Destroy (existing resources)") } - } diff --git a/variables.tf b/variables.tf index 67257e2b..f86b04c3 100644 --- a/variables.tf +++ b/variables.tf @@ -12,19 +12,19 @@ variable "name" { description = "The name to give the Postgresql instance." } -variable "pg_version" { +variable "postgresql_version" { type = string description = "Version of the PostgreSQL instance. If no value is passed, the current preferred version of IBM Cloud Databases is used." default = null validation { condition = anytrue([ - var.pg_version == null, - var.pg_version == "17", - var.pg_version == "16", - var.pg_version == "15", - var.pg_version == "14", - var.pg_version == "13", + var.postgresql_version == null, + var.postgresql_version == "17", + var.postgresql_version == "16", + var.postgresql_version == "15", + var.postgresql_version == "14", + var.postgresql_version == "13", ]) error_message = "Version must be 12, 13, 14, 15 or 16. If no value passed, the current ICD preferred version is used." } @@ -53,30 +53,30 @@ variable "members" { # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } -variable "member_cpu_count" { +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-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations" + description = "Allocated dedicated CPU per member. For shared CPU, set to 0. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling)" default = 0 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } -variable "member_disk_mb" { +variable "disk_mb" { type = number - description = "Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations" + description = "Allocated disk per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling)" default = 5120 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } variable "member_host_flavor" { type = string - description = "Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor). Ignored during restore and point in time recovery operations" + description = "Allocated host flavor per member. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/database#host_flavor)." default = null # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } -variable "member_memory_mb" { +variable "memory_mb" { type = number - description = "Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling). Ignored during restore and point in time recovery operations" + description = "Allocated memory per member. [Learn more](https://cloud.ibm.com/docs/databases-for-postgresql?topic=databases-for-postgresql-resources-scaling)" default = 4096 # Validation is done in the Terraform plan phase by the IBM provider, so no need to add extra validation here. } @@ -260,7 +260,10 @@ variable "use_ibm_owned_encryption_key" { default = true validation { - condition = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? false : true + condition = !( + var.use_ibm_owned_encryption_key == true && + (var.kms_key_crn != null || var.backup_encryption_key_crn != null) + ) error_message = "When 'use_ibm_owned_encryption_key' is true, 'kms_key_crn' and 'backup_encryption_key_crn' must both be null." } @@ -275,7 +278,7 @@ variable "use_ibm_owned_encryption_key" { var.backup_encryption_key_crn == null || (!var.use_default_backup_encryption_key && !var.use_same_kms_key_for_backups) ) - error_message = "When passing a value for backup_encryption_key_crn, you should set use_same_kms_key_for_backups to false, use_default_backup_encryption_key to false and use_ibm_owned_encryption_key to false." + error_message = "When passing a value for 'backup_encryption_key_crn' you cannot set 'use_default_backup_encryption_key' to true or 'use_ibm_owned_encryption_key' to false." } validation { @@ -288,6 +291,12 @@ variable "use_ibm_owned_encryption_key" { } } +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 `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." + default = false +} + 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)." @@ -324,12 +333,6 @@ variable "backup_encryption_key_crn" { } } -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 `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." - default = false -} - variable "skip_iam_authorization_policy" { type = bool description = "Set to true to skip the creation of IAM authorization policies that permits all Databases for PostgreSQL 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." diff --git a/version.tf b/version.tf index 4fd7dce2..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" + 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" } } }