From bce7633894187790dde82f62b139aa0baac2f8da Mon Sep 17 00:00:00 2001 From: Md Anam Raihan Date: Thu, 21 Aug 2025 13:38:06 +0530 Subject: [PATCH 1/4] feat: add support for major openshift version upgrade --- examples/basic/outputs.tf | 4 ++++ main.tf | 23 +++++++++++++++++++++-- outputs.tf | 4 ++++ scripts/get_ocp_cluster_version.sh | 22 ++++++++++++++++++++++ variables.tf | 6 ++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100755 scripts/get_ocp_cluster_version.sh diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index 8fc0f174..6a2e56d7 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -6,3 +6,7 @@ output "cluster_name" { value = module.ocp_base.cluster_name description = "The name of the provisioned cluster." } + +output "data_block_ocp_version" { + value = module.ocp_base.data_source_external_ocp_version +} diff --git a/main.tf b/main.tf index 3ca457b1..78c53645 100644 --- a/main.tf +++ b/main.tf @@ -96,6 +96,18 @@ data "ibm_container_cluster_versions" "cluster_versions" { resource_group_id = var.resource_group_id } +locals { + current_ocp_version = tonumber(data.external.get_ocp_cluster_version.result.ocp_version) +} + +data "external" "get_ocp_cluster_version" { + program = ["bash", "${path.module}/scripts/get_ocp_cluster_version.sh"] + + query = { + cluster_name = var.cluster_name + } +} + module "cos_instance" { count = var.enable_registry_storage && !var.use_existing_cos ? 1 : 0 @@ -153,7 +165,10 @@ resource "ibm_container_vpc_cluster" "cluster" { security_groups = local.cluster_security_groups lifecycle { - ignore_changes = [kube_version] + precondition { + condition = local.current_ocp_version < 0 || local.ocp_version_num <= local.current_ocp_version || var.allow_kube_version_upgrade + error_message = "Kube version changes are disabled unless allow_kube_upgrade is set to true." + } } # default workers are mapped to the subnets that are "private" @@ -224,7 +239,11 @@ resource "ibm_container_vpc_cluster" "autoscaling_cluster" { security_groups = local.cluster_security_groups lifecycle { - ignore_changes = [worker_count, kube_version] + ignore_changes = [worker_count] + precondition { + condition = local.current_ocp_version < 0 || local.ocp_version_num <= local.current_ocp_version || var.allow_kube_version_upgrade + error_message = "Kube version changes are disabled unless allow_kube_upgrade is set to true." + } } # default workers are mapped to the subnets that are "private" diff --git a/outputs.tf b/outputs.tf index 5e8853ec..4eda8df7 100644 --- a/outputs.tf +++ b/outputs.tf @@ -109,3 +109,7 @@ output "secrets_manager_integration_config" { description = "Information about the Secrets Manager instance that is used to store the Ingress certificates." value = var.enable_secrets_manager_integration ? ibm_container_ingress_instance.instance[0] : null } + +output "data_source_external_ocp_version" { + value = tonumber(data.external.get_ocp_cluster_version.result.ocp_version) +} diff --git a/scripts/get_ocp_cluster_version.sh b/scripts/get_ocp_cluster_version.sh new file mode 100755 index 00000000..3a87b9f3 --- /dev/null +++ b/scripts/get_ocp_cluster_version.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +CLUSTER_NAME="$1" + +# Login with API key +ibmcloud login --apikey "$IBMCLOUD_API_KEY" >/dev/null 2>&1 + +# Search for the cluster +OUTPUT=$(ibmcloud oc cluster get -c "$CLUSTER_NAME" 2>/dev/null) + +# Extract OCP version +OCP_VERSION=$(echo "$OUTPUT" | grep -i "^Version:" | awk '{print $2}' | cut -d. -f1,2) + +# If nothing was found, return "0" +if [ -z "$OCP_VERSION" ]; then + OCP_VERSION="-1" +fi + +# Return the OCP version in JSON format +echo "{ \"ocp_version\": \"${OCP_VERSION}\" }" + +exit 0 diff --git a/variables.tf b/variables.tf index 711a531b..8137c723 100644 --- a/variables.tf +++ b/variables.tf @@ -40,6 +40,12 @@ variable "vpc_subnets" { description = "Metadata that describes the VPC's subnets. Obtain this information from the VPC where this cluster is created." } +variable "allow_kube_version_upgrade" { + type = bool + description = "Set to true to allow the module to upgrade the kube version of the cluster. If you wish to make any change to the kube version, set this variable to true." + default = false +} + variable "allow_default_worker_pool_replacement" { type = bool description = "(Advanced users) Set to true to allow the module to recreate a default worker pool. If you wish to make any change to the default worker pool which requires the re-creation of the default pool follow these [steps](https://github.com/terraform-ibm-modules/terraform-ibm-base-ocp-vpc?tab=readme-ov-file#important-considerations-for-terraform-and-default-worker-pool)." From 65e16ea8e083b45c28d81e93baa97b8b02afdac1 Mon Sep 17 00:00:00 2001 From: Md Anam Raihan Date: Thu, 28 Aug 2025 12:43:42 +0530 Subject: [PATCH 2/4] feat: add support for openshift major version upgrade --- examples/basic/outputs.tf | 4 -- main.tf | 26 +++++---- modules/get-ocp-version/main.tf | 12 +++++ modules/get-ocp-version/outputs.tf | 8 +++ .../scripts/get_ocp_cluster_version.sh | 54 +++++++++++++++++++ .../scripts/ibm_cloud_login.sh | 11 ++++ modules/get-ocp-version/variables.tf | 14 +++++ modules/get-ocp-version/version.tf | 9 ++++ outputs.tf | 4 -- scripts/get_ocp_cluster_version.sh | 22 -------- variables.tf | 7 +++ 11 files changed, 127 insertions(+), 44 deletions(-) create mode 100644 modules/get-ocp-version/main.tf create mode 100644 modules/get-ocp-version/outputs.tf create mode 100644 modules/get-ocp-version/scripts/get_ocp_cluster_version.sh create mode 100755 modules/get-ocp-version/scripts/ibm_cloud_login.sh create mode 100644 modules/get-ocp-version/variables.tf create mode 100644 modules/get-ocp-version/version.tf delete mode 100755 scripts/get_ocp_cluster_version.sh diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index 6a2e56d7..8fc0f174 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -6,7 +6,3 @@ output "cluster_name" { value = module.ocp_base.cluster_name description = "The name of the provisioned cluster." } - -output "data_block_ocp_version" { - value = module.ocp_base.data_source_external_ocp_version -} diff --git a/main.tf b/main.tf index 78c53645..6cb5177c 100644 --- a/main.tf +++ b/main.tf @@ -96,18 +96,6 @@ data "ibm_container_cluster_versions" "cluster_versions" { resource_group_id = var.resource_group_id } -locals { - current_ocp_version = tonumber(data.external.get_ocp_cluster_version.result.ocp_version) -} - -data "external" "get_ocp_cluster_version" { - program = ["bash", "${path.module}/scripts/get_ocp_cluster_version.sh"] - - query = { - cluster_name = var.cluster_name - } -} - module "cos_instance" { count = var.enable_registry_storage && !var.use_existing_cos ? 1 : 0 @@ -133,6 +121,16 @@ resource "ibm_resource_tag" "cos_access_tag" { tag_type = "access" } +############################################################################## +# OCP current version +############################################################################## + +module "get_ocp_version" { + source = "./modules/get-ocp-version" + cluster_name = var.cluster_name + ibmcloud_api_key = var.ibmcloud_api_key +} + ############################################################################## # Create a OCP Cluster ############################################################################## @@ -166,7 +164,7 @@ resource "ibm_container_vpc_cluster" "cluster" { lifecycle { precondition { - condition = local.current_ocp_version < 0 || local.ocp_version_num <= local.current_ocp_version || var.allow_kube_version_upgrade + condition = module.get_ocp_version.ocp_version < 0 || local.ocp_version_num <= module.get_ocp_version.ocp_version || var.allow_kube_version_upgrade error_message = "Kube version changes are disabled unless allow_kube_upgrade is set to true." } } @@ -241,7 +239,7 @@ resource "ibm_container_vpc_cluster" "autoscaling_cluster" { lifecycle { ignore_changes = [worker_count] precondition { - condition = local.current_ocp_version < 0 || local.ocp_version_num <= local.current_ocp_version || var.allow_kube_version_upgrade + condition = module.get_ocp_version.ocp_version < 0 || local.ocp_version_num <= module.get_ocp_version.ocp_version || var.allow_kube_version_upgrade error_message = "Kube version changes are disabled unless allow_kube_upgrade is set to true." } } diff --git a/modules/get-ocp-version/main.tf b/modules/get-ocp-version/main.tf new file mode 100644 index 00000000..457f4c8e --- /dev/null +++ b/modules/get-ocp-version/main.tf @@ -0,0 +1,12 @@ +############################################################################## +# OCP Cluster Version +############################################################################## + +data "external" "get_ocp_cluster_version" { + program = ["bash", "${path.module}/scripts/get_ocp_cluster_version.sh"] + + query = { + cluster_name = var.cluster_name + ibmcloud_api_key = var.ibmcloud_api_key + } +} diff --git a/modules/get-ocp-version/outputs.tf b/modules/get-ocp-version/outputs.tf new file mode 100644 index 00000000..391d84e6 --- /dev/null +++ b/modules/get-ocp-version/outputs.tf @@ -0,0 +1,8 @@ +############################################################################## +# Outputs +############################################################################## + +output "ocp_version" { + description = "OpenShift version of the cluster" + value = tonumber(data.external.get_ocp_cluster_version.result["ocp_version"]) +} diff --git a/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh b/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh new file mode 100644 index 00000000..a6506b97 --- /dev/null +++ b/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +## Returns the OCP major.minor version for the given cluster +## Script is designed to be used with Terraform "external" data source +## It always outputs JSON (even on error) so Terraform plan/apply does not break + +set -euo pipefail + +login(){ + # Login into the IBM Cloud CLI + echo "[login] Login to IBM Cloud CLI" >&2 + DIR="$(cd "$(dirname "$0")" && pwd)" + "$DIR"/ibm_cloud_login.sh >/dev/null 2>&1 + echo "[login] Login complete" >&2 +} + +get_ocp_version(){ + local cluster_name=$1 + echo " Retrieving OCP version for cluster $cluster_name" >&2 + + local output + output=$(ibmcloud oc cluster get -c "$cluster_name" --output json 2>/dev/null || true) + + local version + version=$(echo "$output" | jq -r '.masterKubeVersion' | cut -d. -f1,2) + + if [[ -z "$version" || "$version" == "null" ]]; then + echo "Could not retrieve version for cluster $cluster_name" >&2 + version="-1" + fi + + echo "$version" +} + +## Always return JSON even if something fails +handle_error() { + echo '{"ocp_version": "-1"}' + exit 0 +} + +## Parse variables passed from Terraform "query" +## (reads stdin JSON and converts to shell vars) +eval "$(jq -r '@sh "CLUSTER_NAME=\(.cluster_name) IBMCLOUD_API_KEY=\(.ibmcloud_api_key)"')" +export IBMCLOUD_API_KEY + +## Trap all errors after parsing input +trap 'handle_error' ERR + +## Login and retrieve version +login +ocp_version=$(get_ocp_version "$CLUSTER_NAME") + +## Return JSON to Terraform +jq -n -r --arg ocp_version "$ocp_version" '{"ocp_version":$ocp_version}' diff --git a/modules/get-ocp-version/scripts/ibm_cloud_login.sh b/modules/get-ocp-version/scripts/ibm_cloud_login.sh new file mode 100755 index 00000000..55239dff --- /dev/null +++ b/modules/get-ocp-version/scripts/ibm_cloud_login.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +attempts=1 +# Expects the environment variable $IBMCLOUD_API_KEY to be set +until ibmcloud login -q --no-region || [ $attempts -ge 3 ]; do + attempts=$((attempts+1)) + echo "Error logging in to IBM Cloud CLI..." >&2 + sleep 5 +done diff --git a/modules/get-ocp-version/variables.tf b/modules/get-ocp-version/variables.tf new file mode 100644 index 00000000..a6671078 --- /dev/null +++ b/modules/get-ocp-version/variables.tf @@ -0,0 +1,14 @@ +############################################################################## +# Input Variables +############################################################################## + +variable "ibmcloud_api_key" { + description = "APIkey that's associated with the account to use, set via environment variable TF_VAR_ibmcloud_api_key" + type = string + sensitive = true +} + +variable "cluster_name" { + type = string + description = "Name of the target IBM Cloud OpenShift Cluster" +} diff --git a/modules/get-ocp-version/version.tf b/modules/get-ocp-version/version.tf new file mode 100644 index 00000000..e1402bf5 --- /dev/null +++ b/modules/get-ocp-version/version.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + external = { + source = "hashicorp/external" + version = ">= 2.2.3" + } + } +} diff --git a/outputs.tf b/outputs.tf index 4eda8df7..5e8853ec 100644 --- a/outputs.tf +++ b/outputs.tf @@ -109,7 +109,3 @@ output "secrets_manager_integration_config" { description = "Information about the Secrets Manager instance that is used to store the Ingress certificates." value = var.enable_secrets_manager_integration ? ibm_container_ingress_instance.instance[0] : null } - -output "data_source_external_ocp_version" { - value = tonumber(data.external.get_ocp_cluster_version.result.ocp_version) -} diff --git a/scripts/get_ocp_cluster_version.sh b/scripts/get_ocp_cluster_version.sh deleted file mode 100755 index 3a87b9f3..00000000 --- a/scripts/get_ocp_cluster_version.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -CLUSTER_NAME="$1" - -# Login with API key -ibmcloud login --apikey "$IBMCLOUD_API_KEY" >/dev/null 2>&1 - -# Search for the cluster -OUTPUT=$(ibmcloud oc cluster get -c "$CLUSTER_NAME" 2>/dev/null) - -# Extract OCP version -OCP_VERSION=$(echo "$OUTPUT" | grep -i "^Version:" | awk '{print $2}' | cut -d. -f1,2) - -# If nothing was found, return "0" -if [ -z "$OCP_VERSION" ]; then - OCP_VERSION="-1" -fi - -# Return the OCP version in JSON format -echo "{ \"ocp_version\": \"${OCP_VERSION}\" }" - -exit 0 diff --git a/variables.tf b/variables.tf index 8137c723..81f67581 100644 --- a/variables.tf +++ b/variables.tf @@ -2,6 +2,13 @@ # Input Variables ############################################################################## +variable "ibmcloud_api_key" { + description = "APIkey that's associated with the account to use, set via environment variable TF_VAR_ibmcloud_api_key" + type = string + sensitive = true + default = null +} + # Resource Group Variables variable "resource_group_id" { type = string From 5780ded54e39349e3a3656a2898a11e294014eba Mon Sep 17 00:00:00 2001 From: Md Anam Raihan Date: Thu, 28 Aug 2025 13:07:58 +0530 Subject: [PATCH 3/4] update script --- modules/get-ocp-version/scripts/get_ocp_cluster_version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh b/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh index a6506b97..978c492b 100644 --- a/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh +++ b/modules/get-ocp-version/scripts/get_ocp_cluster_version.sh @@ -40,7 +40,7 @@ handle_error() { ## Parse variables passed from Terraform "query" ## (reads stdin JSON and converts to shell vars) -eval "$(jq -r '@sh "CLUSTER_NAME=\(.cluster_name) IBMCLOUD_API_KEY=\(.ibmcloud_api_key)"')" +eval "$(jq -r '@sh "cluster_name=\(.cluster_name) IBMCLOUD_API_KEY=\(.ibmcloud_api_key)"')" export IBMCLOUD_API_KEY ## Trap all errors after parsing input @@ -48,7 +48,7 @@ trap 'handle_error' ERR ## Login and retrieve version login -ocp_version=$(get_ocp_version "$CLUSTER_NAME") +ocp_version=$(get_ocp_version "$cluster_name") ## Return JSON to Terraform jq -n -r --arg ocp_version "$ocp_version" '{"ocp_version":$ocp_version}' From b1936032c629958679ad0b027b6597f8cd56b94d Mon Sep 17 00:00:00 2001 From: Md Anam Raihan Date: Thu, 28 Aug 2025 08:14:43 +0000 Subject: [PATCH 4/4] resolve pc --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a456ee54..0732f25b 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,7 @@ Optionally, you need the following permissions to attach Access Management tags | [cbr\_rule](#module\_cbr\_rule) | terraform-ibm-modules/cbr/ibm//modules/cbr-rule-module | 1.32.6 | | [cos\_instance](#module\_cos\_instance) | terraform-ibm-modules/cos/ibm | 8.21.25 | | [existing\_secrets\_manager\_instance\_parser](#module\_existing\_secrets\_manager\_instance\_parser) | terraform-ibm-modules/common-utilities/ibm//modules/crn-parser | 1.2.0 | +| [get\_ocp\_version](#module\_get\_ocp\_version) | ./modules/get-ocp-version | n/a | ### Resources @@ -293,6 +294,7 @@ Optionally, you need the following permissions to attach Access Management tags | [additional\_vpe\_security\_group\_ids](#input\_additional\_vpe\_security\_group\_ids) | Additional security groups to add to all existing load balancers. This comes in addition to the IBM maintained security group. |
object({
master = optional(list(string), [])
registry = optional(list(string), [])
api = optional(list(string), [])
})
| `{}` | no | | [addons](#input\_addons) | Map of OCP cluster add-on versions to install (NOTE: The 'vpc-block-csi-driver' add-on is installed by default for VPC clusters and 'ibm-storage-operator' is installed by default in OCP 4.15 and later, however you can explicitly specify it here if you wish to choose a later version than the default one). For full list of all supported add-ons and versions, see https://cloud.ibm.com/docs/containers?topic=containers-supported-cluster-addon-versions |
object({
debug-tool = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
image-key-synchronizer = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
openshift-data-foundation = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
vpc-file-csi-driver = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
static-route = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
cluster-autoscaler = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
vpc-block-csi-driver = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
ibm-storage-operator = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
openshift-ai = optional(object({
version = optional(string)
parameters_json = optional(string)
}))
})
| `{}` | no | | [allow\_default\_worker\_pool\_replacement](#input\_allow\_default\_worker\_pool\_replacement) | (Advanced users) Set to true to allow the module to recreate a default worker pool. If you wish to make any change to the default worker pool which requires the re-creation of the default pool follow these [steps](https://github.com/terraform-ibm-modules/terraform-ibm-base-ocp-vpc?tab=readme-ov-file#important-considerations-for-terraform-and-default-worker-pool). | `bool` | `false` | no | +| [allow\_kube\_version\_upgrade](#input\_allow\_kube\_version\_upgrade) | Set to true to allow the module to upgrade the kube version of the cluster. If you wish to make any change to the kube version, set this variable to true. | `bool` | `false` | no | | [attach\_ibm\_managed\_security\_group](#input\_attach\_ibm\_managed\_security\_group) | Specify whether to attach the IBM-defined default security group (whose name is kube-) to all worker nodes. Only applicable if `custom_security_group_ids` is set. | `bool` | `true` | no | | [cbr\_rules](#input\_cbr\_rules) | The list of context-based restriction rules to create. |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})), [])
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
}))
| `[]` | no | | [cluster\_config\_endpoint\_type](#input\_cluster\_config\_endpoint\_type) | Specify which type of endpoint to use for cluster config access: 'default', 'private', 'vpe', 'link'. A 'default' value uses the default endpoint of the cluster. | `string` | `"default"` | no | @@ -308,6 +310,7 @@ Optionally, you need the following permissions to attach Access Management tags | [existing\_cos\_id](#input\_existing\_cos\_id) | The COS id of an already existing COS instance to use for OpenShift internal registry storage. Only required if 'enable\_registry\_storage' and 'use\_existing\_cos' are true. | `string` | `null` | no | | [existing\_secrets\_manager\_instance\_crn](#input\_existing\_secrets\_manager\_instance\_crn) | CRN of the Secrets Manager instance where Ingress certificate secrets are stored. If 'enable\_secrets\_manager\_integration' is set to true then this value is required. | `string` | `null` | no | | [force\_delete\_storage](#input\_force\_delete\_storage) | Flag indicating whether or not to delete attached storage when destroying the cluster - Default: false | `bool` | `false` | no | +| [ibmcloud\_api\_key](#input\_ibmcloud\_api\_key) | APIkey that's associated with the account to use, set via environment variable TF\_VAR\_ibmcloud\_api\_key | `string` | `null` | no | | [ignore\_worker\_pool\_size\_changes](#input\_ignore\_worker\_pool\_size\_changes) | Enable if using worker autoscaling. Stops Terraform managing worker count | `bool` | `false` | no | | [kms\_config](#input\_kms\_config) | Use to attach a KMS instance to the cluster. If account\_id is not provided, defaults to the account in use. |
object({
crk_id = string
instance_id = string
private_endpoint = optional(bool, true) # defaults to true
account_id = optional(string) # To attach KMS instance from another account
wait_for_apply = optional(bool, true) # defaults to true so terraform will wait until the KMS is applied to the master, ready and deployed
})
| `null` | no | | [manage\_all\_addons](#input\_manage\_all\_addons) | Instructs Terraform to manage all cluster addons, even if addons were installed outside of the module. If set to 'true' this module destroys any addons that were installed by other sources. | `bool` | `false` | no |