Repository: terraform-ibm-modules/terraform-ibm-icd-postgresql
Pull request #636: use: cross variable validation
Author: login: Aayush-Abhyarthi
Labels: name: released
Created: 2025-05-06T08:19:59Z | Merged: 2025-05-16T15:53:59Z

### Description

https://github.ibm.com/GoldenEye/issues/issues/12402

### Release required?
<!--- Identify the type of release. For information about the changes in a semantic versioning release, see [Release versioning](https://terraform-ibm-modules.github.io/documentation/#/versioning). --->

- [ ] No release
- [ ] Patch release (`x.x.X`)
- [X] Minor release (`x.X.x`)
- [ ] Major release (`X.x.x`)

##### Release notes content

Upgrade to terraform 1.9.0.
Update to used cross object referencing.

### Run the pipeline

If the CI pipeline doesn't run when you create the PR, the PR requires a user with GitHub collaborators access to run the pipeline.

Run the CI pipeline when the PR is ready for review and you expect tests to pass. Add a comment to the PR with the following text:

```
/run pipeline
```

### Checklist for reviewers

- [ ] If relevant, a test for the change is included or updated with this PR.
- [ ] If relevant, documentation for the change is included or updated with this PR.

### For mergers

- Use a conventional commit message to set the release level. Follow the [guidelines](https://terraform-ibm-modules.github.io/documentation/#/merging.md).
- Include information that users need to know about the PR in the commit message. The commit message becomes part of the GitHub release notes.
- Use the **Squash and merge** option.



Diff:
diff --git a/README.md b/README.md
index 0e8fce1f..1ea93dc5 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ To attach access management tags to resources in this module, you need the follo
 
 | Name | Version |
 |------|---------|
-| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3.0 |
+| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9.0 |
 | <a name="requirement_ibm"></a> [ibm](#requirement\_ibm) | >= 1.70.0, <2.0.0 |
 | <a name="requirement_time"></a> [time](#requirement\_time) | >= 0.9.1 |
 
diff --git a/examples/backup/version.tf b/examples/backup/version.tf
index 45c8d81f..6f39952e 100644
--- a/examples/backup/version.tf
+++ b/examples/backup/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main
     # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example).
diff --git a/examples/basic/version.tf b/examples/basic/version.tf
index 8cba3c6a..29c3e350 100644
--- a/examples/basic/version.tf
+++ b/examples/basic/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main
     # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example).
diff --git a/examples/complete/version.tf b/examples/complete/version.tf
index 452f6fa8..a0bcf4c1 100644
--- a/examples/complete/version.tf
+++ b/examples/complete/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main
     # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example).
diff --git a/examples/fscloud/version.tf b/examples/fscloud/version.tf
index 45c8d81f..6f39952e 100644
--- a/examples/fscloud/version.tf
+++ b/examples/fscloud/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main
     # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example).
diff --git a/examples/pitr/version.tf b/examples/pitr/version.tf
index 45c8d81f..6f39952e 100644
--- a/examples/pitr/version.tf
+++ b/examples/pitr/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main
     # module's version.tf (basic example), and 1 example that will always use the latest provider version (complete example).
diff --git a/main.tf b/main.tf
index 25470a22..2c50ccfa 100644
--- a/main.tf
+++ b/main.tf
@@ -8,17 +8,6 @@
 ########################################################################################################################
 
 locals {
-  # Validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400)
-  # tflint-ignore: terraform_unused_declarations
-  validate_kms_values = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? tobool("When passing values for 'kms_key_crn' or 'backup_encryption_key_crn', you must set 'use_ibm_owned_encryption_key' to false. Otherwise unset them to use default encryption.") : true
-  # tflint-ignore: terraform_unused_declarations
-  validate_kms_vars = !var.use_ibm_owned_encryption_key && var.kms_key_crn == null ? tobool("When setting 'use_ibm_owned_encryption_key' to false, a value must be passed for 'kms_key_crn'.") : true
-  # tflint-ignore: terraform_unused_declarations
-  validate_backup_key = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn != null && (var.use_default_backup_encryption_key || var.use_same_kms_key_for_backups) ? tobool("When passing a value for 'backup_encryption_key_crn' you cannot set 'use_default_backup_encryption_key' to true or 'use_ibm_owned_encryption_key' to false.") : true
-  # tflint-ignore: terraform_unused_declarations
-  validate_backup_key_2 = !var.use_ibm_owned_encryption_key && var.backup_encryption_key_crn == null && !var.use_same_kms_key_for_backups ? tobool("When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'.") : true
-  # tflint-ignore: terraform_unused_declarations
-  validate_pitr_vars = (var.pitr_id != null && var.pitr_time == null) || (var.pitr_time != null && var.pitr_id == null) ? tobool("To use Point-In-Time Recovery (PITR), values for both var.pitr_id and var.pitr_time need to be set. Otherwise, unset both of these.") : true
 
   # 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'.
diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md
index 9e854663..dd3cbfdc 100644
--- a/modules/fscloud/README.md
+++ b/modules/fscloud/README.md
@@ -11,7 +11,7 @@ The IBM Cloud Framework for Financial Services mandates the application of an in
 
 | Name | Version |
 |------|---------|
-| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3.0 |
+| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9.0 |
 | <a name="requirement_ibm"></a> [ibm](#requirement\_ibm) | >=1.70.0, <2.0.0 |
 
 ### Modules
diff --git a/modules/fscloud/version.tf b/modules/fscloud/version.tf
index 36ba5643..54b13cac 100644
--- a/modules/fscloud/version.tf
+++ b/modules/fscloud/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # The below tflint-ignore is required because although the below provider is not directly required by this submodule,
     # it is required by consuming modules, and if not set here, the top level module calling this module will not be
diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf
index 45858b69..e5f343d5 100644
--- a/solutions/standard/main.tf
+++ b/solutions/standard/main.tf
@@ -16,13 +16,6 @@ module "resource_group" {
 # TODO: Replace with terraform cross variable validation: https://github.ibm.com/GoldenEye/issues/issues/10836
 #######################################################################################################################
 
-locals {
-  # tflint-ignore: terraform_unused_declarations
-  validate_kms_1 = var.existing_postgresql_instance_crn != null ? true : var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn != null || var.existing_kms_key_crn != null || var.existing_backup_kms_key_crn != null) ? tobool("When setting values for 'existing_kms_instance_crn', 'existing_kms_key_crn' or 'existing_backup_kms_key_crn', the 'use_ibm_owned_encryption_key' input must be set to false.") : true
-  # tflint-ignore: terraform_unused_declarations
-  validate_kms_2 = var.existing_postgresql_instance_crn != null ? true : !var.use_ibm_owned_encryption_key && (var.existing_kms_instance_crn == null && var.existing_kms_key_crn == null) ? tobool("When 'use_ibm_owned_encryption_key' is false, a value is required for either 'existing_kms_instance_crn' (to create a new key), or 'existing_kms_key_crn' to use an existing key.") : true
-}
-
 #######################################################################################################################
 # KMS encryption key
 #######################################################################################################################
@@ -254,10 +247,6 @@ module "postgresql_instance_crn_parser" {
 locals {
   existing_postgresql_guid   = var.existing_postgresql_instance_crn != null ? module.postgresql_instance_crn_parser[0].service_instance : null
   existing_postgresql_region = var.existing_postgresql_instance_crn != null ? module.postgresql_instance_crn_parser[0].region : null
-
-  # Validate the region input matches region detected in existing instance CRN (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400)
-  # tflint-ignore: terraform_unused_declarations
-  validate_existing_instance_region = var.existing_postgresql_instance_crn != null && var.region != local.existing_postgresql_region ? tobool("The region detected in the 'existing_postgresql_instance_crn' value must match the value of the 'region' input variable when passing an existing instance.") : true
 }
 
 # Do a data lookup on the resource GUID to get more info that is needed for the 'ibm_database' data lookup below
diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf
index 47546f27..c248cda5 100644
--- a/solutions/standard/variables.tf
+++ b/solutions/standard/variables.tf
@@ -46,6 +46,11 @@ variable "region" {
   description = "The region where you want to deploy your instance."
   type        = string
   default     = "us-south"
+
+  validation {
+    condition     = var.existing_postgresql_instance_crn != null && var.region != local.existing_postgresql_region ? false : true
+    error_message = "The region detected in the 'existing_postgresql_instance_crn' value must match the value of the 'region' input variable when passing an existing instance."
+  }
 }
 
 variable "pg_version" {
@@ -58,6 +63,14 @@ 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:'"
+  }
 }
 
 variable "remote_leader_crn" {
@@ -224,6 +237,30 @@ variable "use_ibm_owned_encryption_key" {
   type        = bool
   description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for `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."
   default     = false
+
+  # this validation ensures IBM-owned key is not used when KMS details are provided
+  validation {
+    condition = (
+      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
+      ))
+    )
+    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."
+  }
+
+  # 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)."
+  }
 }
 
 variable "existing_kms_instance_crn" {
diff --git a/solutions/standard/version.tf b/solutions/standard/version.tf
index 32b1de6d..7ee41ee2 100644
--- a/solutions/standard/version.tf
+++ b/solutions/standard/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
 
   # Lock DA into an exact provider version - renovate automation will keep it updated
   required_providers {
diff --git a/variables.tf b/variables.tf
index 355cba0f..67257e2b 100644
--- a/variables.tf
+++ b/variables.tf
@@ -258,6 +258,34 @@ variable "use_ibm_owned_encryption_key" {
   type        = bool
   description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input."
   default     = true
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? false : true
+    error_message = "When 'use_ibm_owned_encryption_key' is true, 'kms_key_crn' and 'backup_encryption_key_crn' must both be null."
+  }
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key || var.kms_key_crn != null
+    error_message = "When setting 'use_ibm_owned_encryption_key' to false, a value must be passed for 'kms_key_crn'."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn == null ||
+      (!var.use_default_backup_encryption_key && !var.use_same_kms_key_for_backups)
+    )
+    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."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn != null ||
+      var.use_same_kms_key_for_backups
+    )
+    error_message = "When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'."
+  }
 }
 
 variable "kms_key_crn" {
@@ -358,6 +386,16 @@ variable "pitr_id" {
   type        = string
   description = "(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."
   default     = null
+
+  validation {
+    condition     = var.pitr_id != null ? true : var.pitr_time == null
+    error_message = "To use Point-In-Time Recovery (PITR), a value for var.pitr_id needs to be set when var.pitr_time is specified. Otherwise, unset var.pitr_time."
+  }
+
+  validation {
+    condition     = var.pitr_id == null ? true : var.pitr_time != null
+    error_message = "To use Point-In-Time Recovery (PITR), a value for var.pitr_time needs to be set when var.pitr_id is specified. Otherwise, unset var.pitr_id."
+  }
 }
 
 variable "pitr_time" {
diff --git a/version.tf b/version.tf
index e8ac7f0a..4a4e895e 100644
--- a/version.tf
+++ b/version.tf
@@ -1,5 +1,5 @@
 terraform {
-  required_version = ">= 1.3.0"
+  required_version = ">= 1.9.0"
   required_providers {
     # Use "greater than or equal to" range in modules
     ibm = {


Reviews:
user: login: mukulpalit-ibm
body: Validation missing in root module
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T06:56:43Z
user: login: shemau
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:12:17Z
user: login: mukulpalit-ibm
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T06:35:36Z
user: login: Ak-sky
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T20:39:33Z

Review comments:
diff_hunk: @@ -258,6 +258,34 @@ variable "use_ibm_owned_encryption_key" {
   type        = bool
   description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input."
   default     = true
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? false : true
+    error_message = "When 'use_ibm_owned_encryption_key' is true, 'kms_key_crn' and 'backup_encryption_key_crn' must both be null."
+  }
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key || var.kms_key_crn != null
+    error_message = "When setting 'use_ibm_owned_encryption_key' to false, a value must be passed for 'kms_key_crn'."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn == null ||
+      (!var.use_default_backup_encryption_key && !var.use_same_kms_key_for_backups)
+    )
+    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."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn != null ||
+      var.use_same_kms_key_for_backups
+    )
+    error_message = "When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'."
+  }
path: variables.tf
user: login: mukulpalit-ibm
body: validation missing for validate_pitr_vars
created_at: 2025-05-12T06:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -258,6 +258,34 @@ variable "use_ibm_owned_encryption_key" {
   type        = bool
   description = "IBM Cloud Databases will secure your deployment's data at rest automatically with an encryption key that IBM hold. Alternatively, you may select your own Key Management System instance and encryption key (Key Protect or Hyper Protect Crypto Services) by setting this to false. If setting to false, a value must be passed for the `kms_key_crn` input."
   default     = true
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key && (var.kms_key_crn != null || var.backup_encryption_key_crn != null) ? false : true
+    error_message = "When 'use_ibm_owned_encryption_key' is true, 'kms_key_crn' and 'backup_encryption_key_crn' must both be null."
+  }
+
+  validation {
+    condition     = var.use_ibm_owned_encryption_key || var.kms_key_crn != null
+    error_message = "When setting 'use_ibm_owned_encryption_key' to false, a value must be passed for 'kms_key_crn'."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn == null ||
+      (!var.use_default_backup_encryption_key && !var.use_same_kms_key_for_backups)
+    )
+    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."
+  }
+
+  validation {
+    condition = (
+      var.use_ibm_owned_encryption_key ||
+      var.backup_encryption_key_crn != null ||
+      var.use_same_kms_key_for_backups
+    )
+    error_message = "When 'use_same_kms_key_for_backups' is set to false, a value needs to be passed for 'backup_encryption_key_crn'."
+  }
path: variables.tf
user: login: shemau
body: good catch... I added a tested commit for this.

I opted for more specific (more verbose) validation. This keeps the validation conditional logic simpler and more readable. Feel free to change it if you want.
created_at: 2025-05-13T12:05:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: Aayush-Abhyarthi
created_at: 2025-05-06T08:20:07Z
body: /run pipeline
author_association: MEMBER
user: login: Ak-sky
created_at: 2025-05-06T12:40:46Z
body: @Aayush-Abhyarthi, is this covering all the scenarios like this one here- https://github.com/terraform-ibm-modules/terraform-ibm-icd-redis/pull/563#issue-2987877177?
author_association: MEMBER
user: login: Aayush-Abhyarthi
created_at: 2025-05-08T08:51:11Z
body: > @Aayush-Abhyarthi, is this covering all the scenarios like this one here- [terraform-ibm-modules/terraform-ibm-icd-redis#563 (comment)](https://github.com/terraform-ibm-modules/terraform-ibm-icd-redis/pull/563#issue-2987877177)?

@Ak-sky Yes Akash.
author_association: MEMBER
user: login: Aayush-Abhyarthi
created_at: 2025-05-11T20:24:34Z
body: /run pipeline
author_association: MEMBER
user: login: shemau
created_at: 2025-05-13T12:13:14Z
body: /run pipeline
author_association: CONTRIBUTOR
user: login: Aayush-Abhyarthi
created_at: 2025-05-14T06:40:47Z
body: /run pipeline
author_association: MEMBER
user: login: ocofaigh
created_at: 2025-05-16T14:31:33Z
body: /run pipeline
author_association: CONTRIBUTOR
user: login: terraform-ibm-modules-ops
created_at: 2025-05-16T15:54:47Z
body: :tada: This PR is included in version 3.22.19 :tada:

The release is available on:
- [GitHub release](https://github.com/terraform-ibm-modules/terraform-ibm-icd-postgresql/releases/tag/v3.22.19)
- `v3.22.19`

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR