diff --git a/static/compatibilities.yaml b/static/compatibilities.yaml index 3791bfd46e..f9b00168ba 100644 --- a/static/compatibilities.yaml +++ b/static/compatibilities.yaml @@ -20856,6 +20856,114 @@ addons: chart_version: 3.5.0 images: ['k8s.gcr.io/metrics-server/metrics-server:v0.5.0'] name: metrics-server +- icon: https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/main/charts/nginx-gateway-fabric/chart-icon.png + git_url: https://github.com/nginx/nginx-gateway-fabric + release_url: https://github.com/nginx/nginx-gateway-fabric/releases/tag/v{vsn} + helm_repository_url: oci://ghcr.io/nginx/charts/nginx-gateway-fabric + chart_name: nginx-gateway-fabric + versions: + - version: 2.6.7 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.7 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.6.7', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.6.7'] + - version: 2.6.3 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.3 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.6.3', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.6.3'] + - version: 2.5.1 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.5.1 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.5.1', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.1'] + - version: 2.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'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.4.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.4.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.4.2'] + - version: 2.3.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.3.0 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.3.0', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.3.0'] + - version: 2.2.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.2.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.2.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.2.2'] + - version: 2.1.4 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.1.4 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.1.4', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.1.4'] + - version: 2.0.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.0.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.0.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.0.2'] + - version: 1.6.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.6.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:1.6.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:1.6.2'] + - version: 1.5.1 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.5.1 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.5.1', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.5.1'] + - version: 1.4.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.4.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.4.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.4.0'] + - version: 1.3.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', + '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.3.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.3.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.3.0'] + - version: 1.2.0 + 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'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.2.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.2.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.2.0'] + name: nginx-gateway-fabric - icon: https://avatars.githubusercontent.com/u/49998002?s=48&v=4 git_url: https://github.com/open-telemetry/opentelemetry-operator release_url: https://github.com/open-telemetry/opentelemetry-operator/releases/tag/v{vsn} diff --git a/static/compatibilities/manifest.yaml b/static/compatibilities/manifest.yaml index 2be754b662..686ded9c78 100644 --- a/static/compatibilities/manifest.yaml +++ b/static/compatibilities/manifest.yaml @@ -26,6 +26,7 @@ names: - linkerd - longhorn - metrics-server +- nginx-gateway-fabric - opentelemetry-operator - rook - strimzi-kafka diff --git a/static/compatibilities/nginx-gateway-fabric.yaml b/static/compatibilities/nginx-gateway-fabric.yaml new file mode 100644 index 0000000000..3d23026d50 --- /dev/null +++ b/static/compatibilities/nginx-gateway-fabric.yaml @@ -0,0 +1,107 @@ +icon: https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/main/charts/nginx-gateway-fabric/chart-icon.png +git_url: https://github.com/nginx/nginx-gateway-fabric +release_url: https://github.com/nginx/nginx-gateway-fabric/releases/tag/v{vsn} +helm_repository_url: oci://ghcr.io/nginx/charts/nginx-gateway-fabric +chart_name: nginx-gateway-fabric +versions: +- version: 2.6.7 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.7 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.6.7', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.6.7'] +- version: 2.6.3 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.3 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.6.3', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.6.3'] +- version: 2.5.1 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.5.1 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.5.1', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.5.1'] +- version: 2.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'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.4.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.4.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.4.2'] +- version: 2.3.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.3.0 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.3.0', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.3.0'] +- version: 2.2.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.2.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.2.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.2.2'] +- version: 2.1.4 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.1.4 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.1.4', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.1.4'] +- version: 2.0.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.0.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:2.0.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:2.0.2'] +- version: 1.6.2 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.6.2 + images: ['ghcr.io/nginx/nginx-gateway-fabric:1.6.2', 'ghcr.io/nginx/nginx-gateway-fabric/nginx:1.6.2'] +- version: 1.5.1 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.5.1 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.5.1', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.5.1'] +- version: 1.4.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.4.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.4.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.4.0'] +- version: 1.3.0 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', + '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.3.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.3.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.3.0'] +- version: 1.2.0 + 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'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.2.0 + images: ['ghcr.io/nginxinc/nginx-gateway-fabric:1.2.0', 'ghcr.io/nginxinc/nginx-gateway-fabric/nginx:1.2.0'] diff --git a/utils/compatibility/scrapers/nginx-gateway-fabric.py b/utils/compatibility/scrapers/nginx-gateway-fabric.py new file mode 100644 index 0000000000..f3b35f16f6 --- /dev/null +++ b/utils/compatibility/scrapers/nginx-gateway-fabric.py @@ -0,0 +1,164 @@ +from collections import OrderedDict +import re + +import requests +import yaml + +from utils import ( + current_kube_version, + ensure_keys, + expand_kube_versions, + print_error, + print_success, + read_yaml, + reduce_versions, + update_versions_data, + fetch_page, + validate_semver, + write_yaml, +) + + +APP_NAME = "nginx-gateway-fabric" +COMPATIBILITY_URL = "https://raw.githubusercontent.com/nginx/documentation/main/content/ngf/overview/technical-specifications.md" +CHART_VALUES_URL = "https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/v{version}/charts/nginx-gateway-fabric/values.yaml" +TARGET_FILE = f"../../static/compatibilities/{APP_NAME}.yaml" + + +def _decode(content): + return content.decode("utf-8") if isinstance(content, bytes) else content + + +def _clean_cell(cell): + return re.sub(r"\s+", " ", cell.strip().strip("`").replace("*", "")) + + +def _parse_kube_versions(value): + cleaned = _clean_cell(value).lstrip("v") + plus_match = re.fullmatch(r"(\d+\.\d+)\+", cleaned) + if plus_match: + return expand_kube_versions(plus_match.group(1), current_kube_version()) + + range_match = re.fullmatch(r"(\d+\.\d+)\s*(?:-|–|—)\s*(\d+\.\d+)", cleaned) + if range_match: + return expand_kube_versions(range_match.group(1), range_match.group(2)) + + version_match = re.fullmatch(r"\d+\.\d+", cleaned) + return [cleaned] if version_match else [] + + +def _image_from_config(config, fallback_repository, fallback_tag): + image = config.get("image", {}) if isinstance(config, dict) else {} + repository = image.get("repository") or fallback_repository + tag = image.get("tag") or fallback_tag + return f"{repository}:{tag}" + + +def _fallback_image_repositories(version): + parsed = validate_semver(version) + org = "nginxinc" if parsed and parsed.major == 1 and parsed.minor < 6 else "nginx" + repository = f"ghcr.io/{org}/nginx-gateway-fabric" + return repository, f"{repository}/nginx" + + +def _default_images(version): + control_plane_repository, data_plane_repository = _fallback_image_repositories(version) + fallbacks = [ + f"{control_plane_repository}:{version}", + f"{data_plane_repository}:{version}", + ] + try: + response = requests.get(CHART_VALUES_URL.format(version=version), timeout=10) + except requests.RequestException: + return fallbacks + if response.status_code != 200: + return fallbacks + + try: + values = yaml.safe_load(response.text) or {} + except yaml.YAMLError as exc: + print_error(f"Failed to parse NGINX Gateway Fabric chart values: {exc}") + return fallbacks + + control_plane = _image_from_config( + values.get("nginxGateway", {}), + control_plane_repository, + version, + ) + data_plane = _image_from_config( + values.get("nginx", {}), + data_plane_repository, + version, + ) + return [control_plane, data_plane] + + +def _write_compatibility_info(filepath, new_versions): + data = read_yaml(filepath) or {} + update_versions_data(data, [ensure_keys(version) for version in new_versions]) + data["versions"] = reduce_versions(data["versions"]) + if write_yaml(filepath, data): + print_success(f"Updated compatibility info table: {filepath}") + else: + print_error(f"Failed to update compatibility info for {filepath}") + + +def extract_table_data(content): + rows = [] + in_table = False + + for line in _decode(content).splitlines(): + stripped = line.strip() + if stripped.startswith("| NGINX Gateway Fabric | Gateway API | Kubernetes |"): + in_table = True + continue + if in_table and not stripped.startswith("|"): + break + if not in_table or set(stripped.replace("|", "").strip()) <= {"-"}: + continue + + columns = [_clean_cell(column) for column in stripped.strip("|").split("|")] + if len(columns) < 3: + continue + app_version = columns[0].lstrip("v") + if app_version.lower() == "edge": + continue + version = validate_semver(app_version) + if not version: + continue + + ver = str(version) + kube_versions = _parse_kube_versions(columns[2]) + if not kube_versions: + print_error( + f"Could not parse Kubernetes versions for NGINX Gateway Fabric {ver}: {columns[2]}" + ) + continue + + rows.append( + OrderedDict( + [ + ("version", ver), + ("kube", kube_versions), + ("chart_version", ver), + ("images", _default_images(ver)), + ("requirements", []), + ("incompatibilities", []), + ] + ) + ) + + return rows + + +def scrape(): + page_content = fetch_page(COMPATIBILITY_URL) + if not page_content: + return + + rows = extract_table_data(page_content) + if not rows: + print_error("No NGINX Gateway Fabric compatibility rows found.") + return + + _write_compatibility_info(TARGET_FILE, rows) diff --git a/utils/compatibility/tests/test_nginx_gateway_fabric.py b/utils/compatibility/tests/test_nginx_gateway_fabric.py new file mode 100644 index 0000000000..14e1f2444a --- /dev/null +++ b/utils/compatibility/tests/test_nginx_gateway_fabric.py @@ -0,0 +1,43 @@ +import importlib +import sys +import unittest +from pathlib import Path +from unittest.mock import patch + +COMPATIBILITY_DIR = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(COMPATIBILITY_DIR)) +sys.modules.pop("utils", None) + +scraper = importlib.import_module("scrapers.nginx-gateway-fabric") + + +class NginxGatewayFabricScraperTest(unittest.TestCase): + def test_parse_kube_versions_accepts_ranges_and_rejects_notes(self): + self.assertEqual( + scraper._parse_kube_versions("1.31 - 1.35"), + ["1.31", "1.32", "1.33", "1.34", "1.35"], + ) + self.assertEqual(scraper._parse_kube_versions("see release notes"), []) + + @patch.object( + scraper, + "_default_images", + return_value=["ghcr.io/nginx/nginx-gateway-fabric:2.6.7"], + ) + def test_extract_table_data_skips_edge_release(self, _default_images): + markdown = """\ +| NGINX Gateway Fabric | Gateway API | Kubernetes | +|---|---|---| +| edge | 1.4 | 1.35 | +| 2.6.7 | 1.4 | 1.31 - 1.35 | +""" + + rows = scraper.extract_table_data(markdown) + + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]["version"], "2.6.7") + self.assertEqual(rows[0]["kube"], ["1.31", "1.32", "1.33", "1.34", "1.35"]) + + +if __name__ == "__main__": + unittest.main()