diff --git a/README.md b/README.md
index 7cd4f0d2..f4e6cd93 100644
--- a/README.md
+++ b/README.md
@@ -290,6 +290,7 @@ Optionally, you need the following permissions to attach Access Management tags
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.9.0 |
+| [external](#requirement\_external) | >=2.3.5, <3.0.0 |
| [ibm](#requirement\_ibm) | >= 1.78.2, < 2.0.0 |
| [kubernetes](#requirement\_kubernetes) | >= 2.16.1, < 3.0.0 |
| [null](#requirement\_null) | >= 3.2.1, < 4.0.0 |
@@ -327,9 +328,11 @@ Optionally, you need the following permissions to attach Access Management tags
| [null_resource.install_required_binaries](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [null_resource.ocp_console_management](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [time_sleep.wait_for_auth_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
+| [external_external.ocp_addon_versions](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source |
| [ibm_container_addons.existing_addons](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_addons) | data source |
| [ibm_container_cluster_config.cluster_config](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_cluster_config) | data source |
| [ibm_container_cluster_versions.cluster_versions](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_cluster_versions) | data source |
+| [ibm_iam_auth_token.tokendata](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/iam_auth_token) | data source |
| [ibm_is_lbs.all_lbs](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/is_lbs) | data source |
| [ibm_is_virtual_endpoint_gateway.api_vpe](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/is_virtual_endpoint_gateway) | data source |
| [ibm_is_virtual_endpoint_gateway.master_vpe](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/is_virtual_endpoint_gateway) | data source |
diff --git a/main.tf b/main.tf
index 7d8ee7a7..6b82ac37 100644
--- a/main.tf
+++ b/main.tf
@@ -53,6 +53,20 @@ locals {
binaries_path = "/tmp"
}
+########################################################################################################################
+# Get OCP AI Add-on Versions
+########################################################################################################################
+
+data "ibm_iam_auth_token" "tokendata" {}
+
+data "external" "ocp_addon_versions" {
+ program = ["python3", "${path.module}/scripts/get_ocp_addon_versions.py"]
+ query = {
+ IAM_TOKEN = sensitive(data.ibm_iam_auth_token.tokendata.iam_access_token)
+ REGION = var.region
+ }
+}
+
# Local block to verify validations for OCP AI Addon.
locals {
@@ -65,6 +79,7 @@ locals {
is_gpu = contains(["gx2", "gx3", "gx4"], split(".", pool.machine_type)[0])
}
}
+ ocp_ai_addon_supported_versions = jsondecode(data.external.ocp_addon_versions.result["openshift-ai"])
}
# Separate local block to handle os validations
diff --git a/scripts/get_ocp_addon_versions.py b/scripts/get_ocp_addon_versions.py
new file mode 100644
index 00000000..88e1d9f1
--- /dev/null
+++ b/scripts/get_ocp_addon_versions.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python3
+import http.client
+import json
+import os
+import sys
+from urllib.parse import urlparse
+
+
+def parse_input():
+ """
+ Reads JSON input from stdin and parses it into a dictionary.
+ Returns:
+ dict: Parsed input data.
+ """
+ try:
+ data = json.loads(sys.stdin.read())
+ except json.JSONDecodeError as e:
+ raise ValueError("Invalid JSON input") from e
+ return data
+
+
+def validate_inputs(data):
+ """
+ Validates required inputs 'IAM_TOKEN' and 'REGION' from the parsed input.
+ Args:
+ data (dict): Input data parsed from JSON.
+ Returns:
+ tuple: A tuple containing (IAM_TOKEN, REGION).
+ """
+ token = data.get("IAM_TOKEN")
+ if not token:
+ raise ValueError("IAM_TOKEN is required")
+
+ region = data.get("REGION")
+ if not region:
+ raise ValueError("REGION is required")
+
+ return token, region
+
+
+def get_env_variable():
+ """
+ Retrieves the value of an environment variable.
+ Returns:
+ str: The value of the environment variable.
+ """
+ api_endpoint = os.getenv(
+ "IBMCLOUD_CS_API_ENDPOINT", "https://containers.test.cloud.ibm.com/global"
+ )
+ return api_endpoint
+
+
+def fetch_addon_versions(iam_token, region, api_endpoint):
+ """
+ Fetches openshift add-on versions using HTTP connection.
+ Args:
+ iam_token (str): IBM Cloud IAM token for authentication.
+ region (str): Region to query for add-ons.
+ api_endpoint (str): Base API endpoint URL.
+ Returns:
+ list: Parsed JSON response containing add-on information.
+ """
+ # Add https if user passed just a hostname
+ if not api_endpoint.startswith("https://"):
+ api_endpoint = f"https://{api_endpoint}"
+
+ parsed = urlparse(api_endpoint)
+
+ # Default path to /global if none supplied
+ base_path = parsed.path.rstrip("/") if parsed.path else "/global"
+
+ # Final API path
+ url = f"{base_path}/v1/addons"
+ host = parsed.hostname
+ headers = {
+ "Authorization": f"Bearer {iam_token}",
+ "Accept": "application/json",
+ "X-Region": region,
+ }
+
+ conn = http.client.HTTPSConnection(host)
+ try:
+ conn.request("GET", url, headers=headers)
+ response = conn.getresponse()
+ data = response.read().decode()
+
+ if response.status != 200:
+ raise RuntimeError(
+ f"API request failed: {response.status} {response.reason} - {data}"
+ )
+
+ return json.loads(data)
+ except http.client.HTTPException as e:
+ raise RuntimeError("HTTP request failed") from e
+ finally:
+ conn.close()
+
+
+def transform_addons(addons_data):
+ """
+ Transforms the raw add-on data into a nested dictionary structured by add-on name and version.
+ Args:
+ addons_data: Raw data returned by the add-on API.
+ Returns:
+ dict: Transformed add-on data suitable for Terraform consumption.
+ """
+ result = {}
+
+ for addon in addons_data:
+ name = addon.get("name")
+ version = addon.get("version")
+
+ supported_ocp = addon.get("supportedOCPRange", "unsupported")
+ supported_kube = addon.get("supportedKubeRange", "unsupported")
+
+ if name not in result:
+ result[name] = {}
+
+ result[name][version] = {
+ "supported_openshift_range": supported_ocp,
+ "supported_kubernetes_range": supported_kube,
+ }
+
+ if not result:
+ raise RuntimeError("No add-on data found.")
+
+ return result
+
+
+def format_for_terraform(result):
+ """
+ Converts the transformed add-on data into JSON strings for Terraform external data source consumption.
+ Args:
+ result (dict): Transformed add-on data.
+ Returns:
+ dict: A dictionary mapping add-on names to JSON strings of their version info.
+ """
+ return {name: json.dumps(versions) for name, versions in result.items()}
+
+
+def main():
+ """
+ Main execution function: reads input, validates, fetches API data, transforms it,
+ formats it for Terraform and prints the JSON output.
+ """
+ data = parse_input()
+ iam_token, region = validate_inputs(data)
+ api_endpoint = get_env_variable()
+ api_endpoint = get_env_variable()
+ addons_data = fetch_addon_versions(iam_token, region, api_endpoint)
+ transformed = transform_addons(addons_data)
+ output = format_for_terraform(transformed)
+
+ print(json.dumps(output))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/variables.tf b/variables.tf
index 6aaea3c6..27813ddc 100644
--- a/variables.tf
+++ b/variables.tf
@@ -364,9 +364,30 @@ variable "addons" {
nullable = false
default = {}
+ ########################################################################################################################
+ # OCP AI Addon version validation
+ ########################################################################################################################
+
validation {
- condition = (lookup(var.addons, "openshift-ai", null) != null ? lookup(var.addons["openshift-ai"], "version", null) == null : true) || (tonumber(local.ocp_version_num) >= 4.16)
- error_message = "OCP AI add-on requires OCP version >= 4.16.0"
+ condition = (
+ lookup(var.addons, "openshift-ai", null) == null ||
+ lookup(var.addons["openshift-ai"], "version", null) == null ||
+ (
+ contains(keys(local.ocp_ai_addon_supported_versions), var.addons["openshift-ai"].version) &&
+ (
+ local.ocp_version_num >= tonumber(regexall("\\d+\\.\\d+", split(" ", local.ocp_ai_addon_supported_versions[var.addons["openshift-ai"].version].supported_openshift_range)[0])[0])) &&
+ (
+ local.ocp_version_num < tonumber(regexall("\\d+\\.\\d+", split(" ", local.ocp_ai_addon_supported_versions[var.addons["openshift-ai"].version].supported_openshift_range)[1])[0])
+ )
+ )
+ )
+
+ error_message = (
+ var.addons["openshift-ai"] != null && var.addons["openshift-ai"].version != null ?
+ (contains(keys(local.ocp_ai_addon_supported_versions), var.addons["openshift-ai"].version) ?
+ format("OCP AI add-on version %s requires OCP version %s", var.addons["openshift-ai"].version, local.ocp_ai_addon_supported_versions[var.addons["openshift-ai"].version].supported_openshift_range) :
+ format("OCP AI add-on version %s is not supported.", var.addons["openshift-ai"].version)) : null
+ )
}
validation {
diff --git a/version.tf b/version.tf
index fac8de8b..345a8aae 100644
--- a/version.tf
+++ b/version.tf
@@ -18,5 +18,9 @@ terraform {
source = "hashicorp/time"
version = ">= 0.9.1, < 1.0.0"
}
+ external = {
+ source = "hashicorp/external"
+ version = ">=2.3.5, <3.0.0"
+ }
}
}