From 85a650a57c587af705caa1c2b9581d1b3c651cfb Mon Sep 17 00:00:00 2001 From: KUANPEIEN Date: Mon, 13 Jul 2026 23:33:39 +0700 Subject: [PATCH 1/3] Add Tyk Operator compatibility scraper --- static/compatibilities/manifest.yaml | 1 + static/compatibilities/tyk-operator.yaml | 83 +++++++++ utils/compatibility/scrapers/tyk-operator.py | 182 +++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 static/compatibilities/tyk-operator.yaml create mode 100644 utils/compatibility/scrapers/tyk-operator.py diff --git a/static/compatibilities/manifest.yaml b/static/compatibilities/manifest.yaml index 6d325d796f..0ecb826c03 100644 --- a/static/compatibilities/manifest.yaml +++ b/static/compatibilities/manifest.yaml @@ -1,5 +1,6 @@ names: - kubevirt +- tyk-operator - argo-rollouts - aws-ebs-csi-driver - aws-efs-csi-driver diff --git a/static/compatibilities/tyk-operator.yaml b/static/compatibilities/tyk-operator.yaml new file mode 100644 index 0000000000..366626658e --- /dev/null +++ b/static/compatibilities/tyk-operator.yaml @@ -0,0 +1,83 @@ +icon: https://avatars.githubusercontent.com/u/16858821?s=200&v=4 +git_url: https://github.com/TykTechnologies/tyk-operator +release_url: https://github.com/TykTechnologies/tyk-operator/releases/tag/v{vsn} +readme_url: https://tyk.io/docs/api-management/automations/operator +helm_repository_url: https://helm.tyk.io/public/helm/charts +chart_name: tyk-operator +versions: +- version: 1.4.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] + requirements: [] + incompatibilities: [] + summary: null +- version: 1.4.0 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', + '1.22', '1.21', '1.20', '1.19'] + requirements: [] + incompatibilities: [] + summary: null +- version: 1.3.0 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', + '1.22', '1.21', '1.20', '1.19'] + requirements: [] + incompatibilities: [] + summary: null +- version: 1.2.0 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', + '1.22', '1.21', '1.20', '1.19'] + requirements: [] + incompatibilities: [] + summary: null +- version: 1.1.0 + kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', '1.22', '1.21', + '1.20', '1.19'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.3, + Tyk Gateway 5.5, Tyk Gateway 5.6, Tyk Gateway 5.7] + incompatibilities: [] + summary: null +- version: 1.0.0 + kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', '1.22', '1.21', + '1.20', '1.19'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.3, + Tyk Gateway 5.5, Tyk Gateway 5.6] + incompatibilities: [] + summary: null +- version: 0.18.0 + kube: ['1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.2, + Tyk Gateway 5.3, Tyk Gateway 5.4] + incompatibilities: [] + summary: null +- version: 0.17.0 + kube: ['1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0, + Tyk Gateway 5.2, Tyk Gateway 5.3] + incompatibilities: [] + summary: null +- version: 0.16.0 + kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0, + Tyk Gateway 5.2] + incompatibilities: [] + summary: null +- version: 0.15.0 + kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + incompatibilities: [] + summary: null +- version: 0.14.1 + kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + incompatibilities: [] + summary: null +- version: 0.14.0 + kube: ['1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + incompatibilities: [] + summary: null +- version: 0.13.0 + kube: ['1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] + requirements: [Tyk Gateway 3.2, Tyk Gateway 4.3] + incompatibilities: [] + summary: null diff --git a/utils/compatibility/scrapers/tyk-operator.py b/utils/compatibility/scrapers/tyk-operator.py new file mode 100644 index 0000000000..1c40deb927 --- /dev/null +++ b/utils/compatibility/scrapers/tyk-operator.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import re +from collections import OrderedDict + +from bs4 import BeautifulSoup + +from utils import ( + expand_kube_versions, + fetch_page, + print_error, + read_yaml, + update_chart_versions, + update_compatibility_info, +) + +app_name = "tyk-operator" +compatibility_url = "https://tyk.io/docs/api-management/automations/operator" +release_notes_url = "https://tyk.io/docs/developer-support/release-notes/operator" + + +def get_soup(url: str) -> BeautifulSoup | None: + content = fetch_page(url) + if not content: + return None + return BeautifulSoup(content, "html.parser") + + +def normalize_operator_version(value: str) -> str | None: + match = re.search(r"Tyk Operator\s+v?(\d+\.\d+(?:\.\d+)?)", value) + if not match: + return None + + version = match.group(1) + if re.match(r"^\d+\.\d+$", version): + version = f"{version}.0" + return version + + +def parse_range(value: str) -> list[str]: + match = re.search(r"(\d+\.\d+)\.x\s+to\s+(\d+\.\d+)\.x", value) + if not match: + return [] + + start, end = match.groups() + return list(reversed(expand_kube_versions(start, end))) + + +def parse_release_note_versions(soup: BeautifulSoup) -> dict[str, list[str]]: + versions: dict[str, list[str]] = {} + + for heading in soup.find_all(["h3", "h4"]): + match = re.search(r"(\d+\.\d+\.\d+)\s+Release Notes", heading.get_text(" ", strip=True)) + if not match: + continue + + version = match.group(1) + table = heading.find_next("table") + if not table: + continue + + for row in table.find_all("tr"): + cells = [cell.get_text(" ", strip=True) for cell in row.find_all(["th", "td"])] + if len(cells) < 3 or cells[0] != "Kubernetes": + continue + kube_versions = parse_range(cells[2]) + if kube_versions: + versions[version] = kube_versions + break + + return versions + + +def find_table(soup: BeautifulSoup, first_header: str): + for table in soup.find_all("table"): + first_row = table.find("tr") + if not first_row: + continue + headers = [cell.get_text(" ", strip=True) for cell in first_row.find_all(["th", "td"])] + if headers and headers[0] == first_header: + return table + return None + + +def parse_gateway_compatibility(table) -> dict[str, list[str]]: + if not table: + return {} + + rows = table.find_all("tr") + headers = [cell.get_text(" ", strip=True) for cell in rows[0].find_all(["th", "td"])][1:] + compatibility: dict[str, list[str]] = {} + + for row in rows[1:]: + cells = [cell.get_text(" ", strip=True) for cell in row.find_all(["th", "td"])] + version = normalize_operator_version(cells[0]) if cells else None + if not version: + continue + + requirements = [ + f"Tyk Gateway {gateway_version}" + for gateway_version, value in zip(headers, cells[1:]) + if value.strip().upper() == "Y" + ] + compatibility[version] = requirements + + return compatibility + + +def parse_kube_compatibility(table, gateway_requirements: dict[str, list[str]]) -> list[OrderedDict[str, object]]: + if not table: + return [] + + rows = table.find_all("tr") + headers = [cell.get_text(" ", strip=True) for cell in rows[0].find_all(["th", "td"])][1:] + versions: list[OrderedDict[str, object]] = [] + + for row in rows[1:]: + cells = [cell.get_text(" ", strip=True) for cell in row.find_all(["th", "td"])] + version = normalize_operator_version(cells[0]) if cells else None + if not version: + continue + + kube_versions = [ + kube_version + for kube_version, value in zip(headers, cells[1:]) + if value.strip().upper() == "Y" + ] + if not kube_versions: + continue + + versions.append( + OrderedDict( + [ + ("version", version), + ("kube", list(reversed(kube_versions))), + ("requirements", gateway_requirements.get(version, [])), + ("incompatibilities", []), + ] + ) + ) + + return versions + + +def scrape() -> None: + compatibility_soup = get_soup(compatibility_url) + release_notes_soup = get_soup(release_notes_url) + if not compatibility_soup or not release_notes_soup: + return + + gateway_table = find_table(compatibility_soup, "Tyk Version") + kube_table = find_table(compatibility_soup, "Kubernetes Version") + gateway_requirements = parse_gateway_compatibility(gateway_table) + + version_map: dict[str, OrderedDict[str, object]] = {} + for version, kube_versions in parse_release_note_versions(release_notes_soup).items(): + version_map[version] = OrderedDict( + [ + ("version", version), + ("kube", kube_versions), + ("requirements", gateway_requirements.get(version, [])), + ("incompatibilities", []), + ] + ) + + for version_info in parse_kube_compatibility(kube_table, gateway_requirements): + version = version_info["version"] + if version in version_map: + version_map[version]["requirements"] = version_info["requirements"] + continue + version_map[version] = version_info + + if not version_map: + print_error("No compatibility information found for Tyk Operator") + return + + output_path = f"../../static/compatibilities/{app_name}.yaml" + update_compatibility_info(output_path, list(version_map.values())) + + compatibility_yaml = read_yaml(output_path) + if compatibility_yaml and compatibility_yaml.get("helm_repository_url"): + update_chart_versions(app_name) From 2fad841ebb0e749cbacb4f3d84ccd579bbd87e3f Mon Sep 17 00:00:00 2001 From: KUANPEIEN Date: Tue, 14 Jul 2026 00:10:24 +0700 Subject: [PATCH 2/3] Clarify Tyk Operator scraper source priority --- utils/compatibility/scrapers/tyk-operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/compatibility/scrapers/tyk-operator.py b/utils/compatibility/scrapers/tyk-operator.py index 1c40deb927..05e0ce8eb8 100644 --- a/utils/compatibility/scrapers/tyk-operator.py +++ b/utils/compatibility/scrapers/tyk-operator.py @@ -166,7 +166,8 @@ def scrape() -> None: for version_info in parse_kube_compatibility(kube_table, gateway_requirements): version = version_info["version"] if version in version_map: - version_map[version]["requirements"] = version_info["requirements"] + # Release notes provide the broader tested/compatible Kubernetes range. + # Keep that range when both sources mention the same operator version. continue version_map[version] = version_info From 0e6c06c33e87fba307aea85c5ddb8e166006338c Mon Sep 17 00:00:00 2001 From: KUANPEIEN Date: Mon, 17 Aug 2026 20:51:56 +0700 Subject: [PATCH 3/3] Fix Tyk compatibility requirements schema --- static/compatibilities/tyk-operator.yaml | 111 ++++++++++++++++--- utils/compatibility/scrapers/tyk-operator.py | 2 +- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/static/compatibilities/tyk-operator.yaml b/static/compatibilities/tyk-operator.yaml index 366626658e..ea5de5cb12 100644 --- a/static/compatibilities/tyk-operator.yaml +++ b/static/compatibilities/tyk-operator.yaml @@ -32,52 +32,135 @@ versions: - version: 1.1.0 kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.3, - Tyk Gateway 5.5, Tyk Gateway 5.6, Tyk Gateway 5.7] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '5.0' + - name: Tyk Gateway + version: '5.3' + - name: Tyk Gateway + version: '5.5' + - name: Tyk Gateway + version: '5.6' + - name: Tyk Gateway + version: '5.7' incompatibilities: [] summary: null - version: 1.0.0 kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.3, - Tyk Gateway 5.5, Tyk Gateway 5.6] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '5.0' + - name: Tyk Gateway + version: '5.3' + - name: Tyk Gateway + version: '5.5' + - name: Tyk Gateway + version: '5.6' incompatibilities: [] summary: null - version: 0.18.0 kube: ['1.29', '1.28', '1.27', '1.26', '1.25'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 5.0, Tyk Gateway 5.2, - Tyk Gateway 5.3, Tyk Gateway 5.4] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '5.0' + - name: Tyk Gateway + version: '5.2' + - name: Tyk Gateway + version: '5.3' + - name: Tyk Gateway + version: '5.4' incompatibilities: [] summary: null - version: 0.17.0 kube: ['1.29', '1.28', '1.27', '1.26', '1.25'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0, - Tyk Gateway 5.2, Tyk Gateway 5.3] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '4.3' + - name: Tyk Gateway + version: '5.0' + - name: Tyk Gateway + version: '5.2' + - name: Tyk Gateway + version: '5.3' incompatibilities: [] summary: null - version: 0.16.0 kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0, - Tyk Gateway 5.2] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '4.3' + - name: Tyk Gateway + version: '5.0' + - name: Tyk Gateway + version: '5.2' incompatibilities: [] summary: null - version: 0.15.0 kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '4.3' + - name: Tyk Gateway + version: '5.0' incompatibilities: [] summary: null - version: 0.14.1 kube: ['1.26', '1.25', '1.24', '1.23', '1.22', '1.21', '1.20'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '4.3' + - name: Tyk Gateway + version: '5.0' incompatibilities: [] summary: null - version: 0.14.0 kube: ['1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.0, Tyk Gateway 4.3, Tyk Gateway 5.0] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.0' + - name: Tyk Gateway + version: '4.3' + - name: Tyk Gateway + version: '5.0' incompatibilities: [] summary: null - version: 0.13.0 kube: ['1.25', '1.24', '1.23', '1.22', '1.21', '1.20', '1.19'] - requirements: [Tyk Gateway 3.2, Tyk Gateway 4.3] + requirements: + - name: Tyk Gateway + version: '3.2' + - name: Tyk Gateway + version: '4.3' incompatibilities: [] summary: null diff --git a/utils/compatibility/scrapers/tyk-operator.py b/utils/compatibility/scrapers/tyk-operator.py index 05e0ce8eb8..8ebda69e80 100644 --- a/utils/compatibility/scrapers/tyk-operator.py +++ b/utils/compatibility/scrapers/tyk-operator.py @@ -97,7 +97,7 @@ def parse_gateway_compatibility(table) -> dict[str, list[str]]: continue requirements = [ - f"Tyk Gateway {gateway_version}" + OrderedDict([("name", "Tyk Gateway"), ("version", gateway_version)]) for gateway_version, value in zip(headers, cells[1:]) if value.strip().upper() == "Y" ]