From e5d1af7d78c8a1f777c9b90bdf2c8e747c969d69 Mon Sep 17 00:00:00 2001 From: zikra-iqbal Date: Wed, 3 Dec 2025 13:29:53 +0530 Subject: [PATCH 1/5] feat: Add validation for OCP AI add-ons --- main.tf | 13 ++++++ scripts/get_ocp_ai_addon_versions.sh | 62 ++++++++++++++++++++++++++++ variables.tf | 17 +++++++- 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 scripts/get_ocp_ai_addon_versions.sh diff --git a/main.tf b/main.tf index 191e391e..0e16f4c4 100644 --- a/main.tf +++ b/main.tf @@ -51,6 +51,18 @@ locals { disable_outbound_traffic_protection = startswith(local.ocp_version, "4.14") ? null : var.disable_outbound_traffic_protection } +######################################################################################################################## +# Get OCP AI Add-on Versions +######################################################################################################################## + +data "external" "ocp_ai_addon_versions" { + program = ["bash", "${path.module}/scripts/get_ocp_ai_addon_versions.sh"] + query = { + ibmcloud_api_key = "xxxxxxxxxxxxxxxx", # Not sure where to get the API key; it’s required to log in to the IBM Cloud CLI. + region = var.region + } +} + # Local block to verify validations for OCP AI Addon. locals { @@ -63,6 +75,7 @@ locals { is_gpu = contains(["gx2", "gx3", "gx4"], split(".", pool.machine_type)[0]) } } + ocp_addon_versions_map = data.external.ocp_ai_addon_versions.result } # Separate local block to handle os validations diff --git a/scripts/get_ocp_ai_addon_versions.sh b/scripts/get_ocp_ai_addon_versions.sh new file mode 100644 index 00000000..6bcdfe73 --- /dev/null +++ b/scripts/get_ocp_ai_addon_versions.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -euo pipefail + +############################################# +# Read Input +############################################# + +INPUT="$(tee)" +REGION="$(echo "$INPUT" | jq -r '.region // empty')" +IBMCLOUD_API_KEY="$(echo "$INPUT" | jq -r '.ibmcloud_api_key // empty')" + +############################################# +# Validate Input +############################################# + +if [[ -z "$IBMCLOUD_API_KEY" ]]; then + echo "Error: IBMCLOUD_API_KEY is required." >&2 + exit 1 +fi + +if [[ -z "$REGION" ]]; then + echo "Error: REGION is required." >&2 + exit 1 +fi + +############################################# +# IBM Cloud Login +############################################# + +if ! ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$REGION" >&2; then + echo "Error: Failed to authenticate with IBM Cloud." >&2 + exit 1 +fi + +############################################# +# Fetch and Extract 'openshift-ai' Add-on Versions +############################################# + +OCP_AI_ADDON_VERSION_LIST="$(ibmcloud oc cluster addon versions --output json 2>/dev/null | jq -r '.[] | select(.name == "openshift-ai") | "\(.version) \(.supportedOCPRange)"' || true)" + +if [[ -z "$OCP_AI_ADDON_VERSION_LIST" ]]; then + echo "Error: Failed to retrieve or parse openshift-ai add-on versions." >&2 + exit 1 +fi + + +############################################# +# Convert to JSON Output +############################################# + +OUTPUT_JSON="$(echo "$OCP_AI_ADDON_VERSION_LIST" | jq -R -s -c 'split("\n")[:-1] | map(split(" ")) | map({ (.[0]): (.[1] + " " + .[2]) }) | add')" + +if [[ -z "$OUTPUT_JSON" ]]; then + echo "Error: Failed to produce JSON output." >&2 + exit 1 +fi + +############################################# +# Final Output +############################################# + +echo "$OUTPUT_JSON" diff --git a/variables.tf b/variables.tf index 3d300c26..8878d6b5 100644 --- a/variables.tf +++ b/variables.tf @@ -364,9 +364,22 @@ 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_addon_versions_map), var.addons["openshift-ai"].version) && + (local.ocp_version_num >= tonumber(regexall("\\d+\\.\\d+", split(" ", lookup(local.ocp_addon_versions_map, var.addons["openshift-ai"].version, null))[0])[0])) && + (local.ocp_version_num < tonumber(regexall("\\d+\\.\\d+", split(" ", lookup(local.ocp_addon_versions_map, var.addons["openshift-ai"].version, null))[1])[0])) + ) + ) + error_message = (var.addons["openshift-ai"] != null && var.addons["openshift-ai"].version != null) ? (contains(keys(local.ocp_addon_versions_map), var.addons["openshift-ai"].version) ? + format("OCP AI add-on version: %s requires OCP version %s", var.addons["openshift-ai"].version, local.ocp_addon_versions_map[var.addons["openshift-ai"].version]) : + format("OCP AI add-on version: %s is not supported.", var.addons["openshift-ai"].version)) : null } validation { From 0b910ee5212f57e84f94c53cf9258ce9cea50bf1 Mon Sep 17 00:00:00 2001 From: zikra-iqbal Date: Thu, 4 Dec 2025 12:04:49 +0530 Subject: [PATCH 2/5] chore: Addressed review comments --- main.tf | 12 +-- scripts/get_ocp_addon_versions.py | 106 +++++++++++++++++++++++++++ scripts/get_ocp_ai_addon_versions.sh | 62 ---------------- variables.tf | 20 +++-- 4 files changed, 127 insertions(+), 73 deletions(-) create mode 100644 scripts/get_ocp_addon_versions.py delete mode 100644 scripts/get_ocp_ai_addon_versions.sh diff --git a/main.tf b/main.tf index 65141d13..c6418456 100644 --- a/main.tf +++ b/main.tf @@ -57,11 +57,13 @@ locals { # Get OCP AI Add-on Versions ######################################################################################################################## -data "external" "ocp_ai_addon_versions" { - program = ["bash", "${path.module}/scripts/get_ocp_ai_addon_versions.sh"] +data "ibm_iam_auth_token" "tokendata" {} + +data "external" "ocp_addon_versions" { + program = ["python3", "${path.module}/scripts/get_ocp_addon_versions.py"] query = { - ibmcloud_api_key = "xxxxxxxxxxxxxxxx", # Not sure where to get the API key; it’s required to log in to the IBM Cloud CLI. - region = var.region + IAM_TOKEN = sensitive(data.ibm_iam_auth_token.tokendata.iam_access_token) + region = var.region } } @@ -77,7 +79,7 @@ locals { is_gpu = contains(["gx2", "gx3", "gx4"], split(".", pool.machine_type)[0]) } } - ocp_addon_versions_map = data.external.ocp_ai_addon_versions.result + 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..d40cef21 --- /dev/null +++ b/scripts/get_ocp_addon_versions.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +import json +import sys +import urllib.error +import urllib.request + + +################################ +# Read input from stdin +################################ +def parse_input(): + try: + data = json.loads(sys.stdin.read()) + except json.JSONDecodeError: + error("Invalid JSON input") + return data + + +################################ +# Validate input values +################################ +def validate_inputs(data): + token = data.get("IAM_TOKEN") + if not token: + error("IAM_TOKEN is required") + + region = data.get("region") + if not region: + error("region is required") + return token, region + + +################################ +# API Call for add-on versions +################################ +def fetch_addon_versions(iam_token, region): + url = "https://containers.cloud.ibm.com/global/v1/addons" + headers = { + "Authorization": f"Bearer {iam_token}", + "Accept": "application/json", + "X-Region": region, + } + + try: + req = urllib.request.Request(url, headers=headers) + with urllib.request.urlopen(req) as resp: + return json.load(resp) + except urllib.error.URLError as e: + error(f"Failed to fetch add-on versions: {e}") + + +################################ +# Data transformation +################################ +def transform_addons(addons_data): + 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: + error("No add-on data found.") + + return result + + +def format_for_terraform(result): + return {name: json.dumps(versions) for name, versions in result.items()} + + +################################ +# Failure handling +################################ +def error(msg): + print(f"Error: {msg}", file=sys.stderr) + sys.exit(1) + + +################################ +# Main function +################################ +def main(): + data = parse_input() + iam_token, region = validate_inputs(data) + + addons_data = fetch_addon_versions(iam_token, region) + transformed = transform_addons(addons_data) + output = format_for_terraform(transformed) + + print(json.dumps(output)) + + +if __name__ == "__main__": + main() diff --git a/scripts/get_ocp_ai_addon_versions.sh b/scripts/get_ocp_ai_addon_versions.sh deleted file mode 100644 index 6bcdfe73..00000000 --- a/scripts/get_ocp_ai_addon_versions.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -set -euo pipefail - -############################################# -# Read Input -############################################# - -INPUT="$(tee)" -REGION="$(echo "$INPUT" | jq -r '.region // empty')" -IBMCLOUD_API_KEY="$(echo "$INPUT" | jq -r '.ibmcloud_api_key // empty')" - -############################################# -# Validate Input -############################################# - -if [[ -z "$IBMCLOUD_API_KEY" ]]; then - echo "Error: IBMCLOUD_API_KEY is required." >&2 - exit 1 -fi - -if [[ -z "$REGION" ]]; then - echo "Error: REGION is required." >&2 - exit 1 -fi - -############################################# -# IBM Cloud Login -############################################# - -if ! ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$REGION" >&2; then - echo "Error: Failed to authenticate with IBM Cloud." >&2 - exit 1 -fi - -############################################# -# Fetch and Extract 'openshift-ai' Add-on Versions -############################################# - -OCP_AI_ADDON_VERSION_LIST="$(ibmcloud oc cluster addon versions --output json 2>/dev/null | jq -r '.[] | select(.name == "openshift-ai") | "\(.version) \(.supportedOCPRange)"' || true)" - -if [[ -z "$OCP_AI_ADDON_VERSION_LIST" ]]; then - echo "Error: Failed to retrieve or parse openshift-ai add-on versions." >&2 - exit 1 -fi - - -############################################# -# Convert to JSON Output -############################################# - -OUTPUT_JSON="$(echo "$OCP_AI_ADDON_VERSION_LIST" | jq -R -s -c 'split("\n")[:-1] | map(split(" ")) | map({ (.[0]): (.[1] + " " + .[2]) }) | add')" - -if [[ -z "$OUTPUT_JSON" ]]; then - echo "Error: Failed to produce JSON output." >&2 - exit 1 -fi - -############################################# -# Final Output -############################################# - -echo "$OUTPUT_JSON" diff --git a/variables.tf b/variables.tf index 3eef50b5..27813ddc 100644 --- a/variables.tf +++ b/variables.tf @@ -372,14 +372,22 @@ variable "addons" { condition = ( lookup(var.addons, "openshift-ai", null) == null || lookup(var.addons["openshift-ai"], "version", null) == null || - (contains(keys(local.ocp_addon_versions_map), var.addons["openshift-ai"].version) && - (local.ocp_version_num >= tonumber(regexall("\\d+\\.\\d+", split(" ", lookup(local.ocp_addon_versions_map, var.addons["openshift-ai"].version, null))[0])[0])) && - (local.ocp_version_num < tonumber(regexall("\\d+\\.\\d+", split(" ", lookup(local.ocp_addon_versions_map, var.addons["openshift-ai"].version, null))[1])[0])) + ( + 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_addon_versions_map), var.addons["openshift-ai"].version) ? - format("OCP AI add-on version: %s requires OCP version %s", var.addons["openshift-ai"].version, local.ocp_addon_versions_map[var.addons["openshift-ai"].version]) : - format("OCP AI add-on version: %s is not supported.", var.addons["openshift-ai"].version)) : null + + 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 { From 2d5779d1f0cd7f16eb1f5a8f8b2a620af9079aa5 Mon Sep 17 00:00:00 2001 From: zikra-iqbal Date: Thu, 4 Dec 2025 12:05:38 +0530 Subject: [PATCH 3/5] fix: pre-commit hooks --- README.md | 3 +++ version.tf | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 7cd4f0d2..caff3438 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 | | [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/version.tf b/version.tf index fac8de8b..f8854afa 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" + } } } From 28a647612e8c20aeb8b5bfe971654933cf8d7883 Mon Sep 17 00:00:00 2001 From: zikra-iqbal Date: Thu, 4 Dec 2025 14:16:02 +0530 Subject: [PATCH 4/5] fix: Updated python script --- main.tf | 2 +- scripts/get_ocp_addon_versions.py | 100 +++++++++++++++++++----------- 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/main.tf b/main.tf index c6418456..6b82ac37 100644 --- a/main.tf +++ b/main.tf @@ -63,7 +63,7 @@ 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 + REGION = var.region } } diff --git a/scripts/get_ocp_addon_versions.py b/scripts/get_ocp_addon_versions.py index d40cef21..b7873fa1 100644 --- a/scripts/get_ocp_addon_versions.py +++ b/scripts/get_ocp_addon_versions.py @@ -1,58 +1,84 @@ #!/usr/bin/env python3 +import http.client import json import sys -import urllib.error -import urllib.request -################################ -# Read input from stdin -################################ 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: - error("Invalid JSON input") + except json.JSONDecodeError as e: + raise ValueError("Invalid JSON input") from e return data -################################ -# Validate input values -################################ 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: - error("IAM_TOKEN is required") + raise ValueError("IAM_TOKEN is required") - region = data.get("region") + region = data.get("REGION") if not region: - error("region is required") + raise ValueError("REGION is required") + return token, region -################################ -# API Call for add-on versions -################################ def fetch_addon_versions(iam_token, region): - url = "https://containers.cloud.ibm.com/global/v1/addons" + """ + 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. + Returns: + list: Parsed JSON response containing add-on information. + """ + url = "/global/v1/addons" + host = "containers.cloud.ibm.com" headers = { "Authorization": f"Bearer {iam_token}", "Accept": "application/json", "X-Region": region, } + conn = http.client.HTTPSConnection(host) try: - req = urllib.request.Request(url, headers=headers) - with urllib.request.urlopen(req) as resp: - return json.load(resp) - except urllib.error.URLError as e: - error(f"Failed to fetch add-on versions: {e}") + 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() -################################ -# Data transformation -################################ 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: @@ -71,27 +97,27 @@ def transform_addons(addons_data): } if not result: - error("No add-on data found.") + 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()} -################################ -# Failure handling -################################ -def error(msg): - print(f"Error: {msg}", file=sys.stderr) - sys.exit(1) - - -################################ -# Main function -################################ 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) From f7a24d1a6f2dd92045a2f9edba181cba94fc159c Mon Sep 17 00:00:00 2001 From: zikra-iqbal Date: Thu, 4 Dec 2025 21:35:10 +0530 Subject: [PATCH 5/5] Addressed review comments --- README.md | 2 +- scripts/get_ocp_addon_versions.py | 36 ++++++++++++++++++++++++++----- version.tf | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index caff3438..f4e6cd93 100644 --- a/README.md +++ b/README.md @@ -290,7 +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 | +| [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 | diff --git a/scripts/get_ocp_addon_versions.py b/scripts/get_ocp_addon_versions.py index b7873fa1..88e1d9f1 100644 --- a/scripts/get_ocp_addon_versions.py +++ b/scripts/get_ocp_addon_versions.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import http.client import json +import os import sys +from urllib.parse import urlparse def parse_input(): @@ -36,17 +38,40 @@ def validate_inputs(data): return token, region -def fetch_addon_versions(iam_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. """ - url = "/global/v1/addons" - host = "containers.cloud.ibm.com" + # 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", @@ -120,8 +145,9 @@ def main(): """ data = parse_input() iam_token, region = validate_inputs(data) - - addons_data = fetch_addon_versions(iam_token, region) + 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) diff --git a/version.tf b/version.tf index f8854afa..345a8aae 100644 --- a/version.tf +++ b/version.tf @@ -20,7 +20,7 @@ terraform { } external = { source = "hashicorp/external" - version = "~> 2" + version = ">=2.3.5, <3.0.0" } } }