diff --git a/static/compatibilities.yaml b/static/compatibilities.yaml index 3791bfd46e..b61a073211 100644 --- a/static/compatibilities.yaml +++ b/static/compatibilities.yaml @@ -17294,6 +17294,90 @@ addons: images: ['registry.k8s.io/ingress-nginx/controller:v1.3.1@sha256:54f7fe2c6c5a9db9a0ebf1131797109bb7a4d91f56b9b362bde2abd237dd1974', 'registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.3.0@sha256:549e71a6ca248c5abd51cdb73dbc3083df62cf92ed5e6147c780e30f7e007a47'] name: ingress-nginx +- icon: https://raw.githubusercontent.com/nginx/kubernetes-ingress/main/charts/nginx-ingress/chart-icon.png + git_url: https://github.com/nginx/kubernetes-ingress + release_url: https://github.com/nginx/kubernetes-ingress/releases/tag/v{vsn} + helm_repository_url: oci://ghcr.io/nginx/charts/nginx-ingress + chart_name: nginx-ingress + versions: + - version: 5.5.0 + kube: ['1.36', '1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.0 + images: ['nginx/nginx-ingress:5.5.0'] + - version: 5.4.3 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.5.3 + images: ['nginx/nginx-ingress:5.4.3'] + - version: 5.3.4 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.4.4 + images: ['nginx/nginx-ingress:5.3.4'] + - version: 5.2.1 + kube: ['1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.3.1 + images: ['nginx/nginx-ingress:5.2.1'] + - version: 5.1.1 + kube: ['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: ['nginx/nginx-ingress:5.1.1'] + - version: 5.0.0 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.1.0 + images: ['nginx/nginx-ingress:5.0.0'] + - version: 4.0.1 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.0.1 + images: ['nginx/nginx-ingress:4.0.1'] + - version: 3.7.2 + kube: ['1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.4.2 + images: ['nginx/nginx-ingress:3.7.2'] + - version: 3.6.2 + kube: ['1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.3.2 + images: ['nginx/nginx-ingress:3.6.2'] + - version: 3.5.2 + kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.2.2 + images: ['nginx/nginx-ingress:3.5.2'] + - version: 3.4.3 + kube: ['1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.1.3 + images: ['nginx/nginx-ingress:3.4.3'] + name: nginx-ingress-controller - icon: https://raw.githubusercontent.com/pluralsh/plural-artifacts/main/istio/plural/icons/istio.png?raw=true git_url: https://github.com/istio/istio release_url: https://github.com/istio/istio/releases/tag/{vsn} diff --git a/static/compatibilities/manifest.yaml b/static/compatibilities/manifest.yaml index 2be754b662..ad36ff36c1 100644 --- a/static/compatibilities/manifest.yaml +++ b/static/compatibilities/manifest.yaml @@ -17,6 +17,7 @@ names: - external-secrets - flux - ingress-nginx +- nginx-ingress-controller - istio - jaeger - karpenter diff --git a/static/compatibilities/nginx-ingress-controller.yaml b/static/compatibilities/nginx-ingress-controller.yaml new file mode 100644 index 0000000000..447bfc243b --- /dev/null +++ b/static/compatibilities/nginx-ingress-controller.yaml @@ -0,0 +1,83 @@ +icon: https://raw.githubusercontent.com/nginx/kubernetes-ingress/main/charts/nginx-ingress/chart-icon.png +git_url: https://github.com/nginx/kubernetes-ingress +release_url: https://github.com/nginx/kubernetes-ingress/releases/tag/v{vsn} +helm_repository_url: oci://ghcr.io/nginx/charts/nginx-ingress +chart_name: nginx-ingress +versions: +- version: 5.5.0 + kube: ['1.36', '1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.6.0 + images: ['nginx/nginx-ingress:5.5.0'] +- version: 5.4.3 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.5.3 + images: ['nginx/nginx-ingress:5.4.3'] +- version: 5.3.4 + kube: ['1.35', '1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.4.4 + images: ['nginx/nginx-ingress:5.3.4'] +- version: 5.2.1 + kube: ['1.34', '1.33', '1.32', '1.31', '1.30', '1.29', '1.28', '1.27'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.3.1 + images: ['nginx/nginx-ingress:5.2.1'] +- version: 5.1.1 + kube: ['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: ['nginx/nginx-ingress:5.1.1'] +- version: 5.0.0 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.1.0 + images: ['nginx/nginx-ingress:5.0.0'] +- version: 4.0.1 + kube: ['1.32', '1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 2.0.1 + images: ['nginx/nginx-ingress:4.0.1'] +- version: 3.7.2 + kube: ['1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.4.2 + images: ['nginx/nginx-ingress:3.7.2'] +- version: 3.6.2 + kube: ['1.31', '1.30', '1.29', '1.28', '1.27', '1.26', '1.25'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.3.2 + images: ['nginx/nginx-ingress:3.6.2'] +- version: 3.5.2 + kube: ['1.30', '1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.2.2 + images: ['nginx/nginx-ingress:3.5.2'] +- version: 3.4.3 + kube: ['1.29', '1.28', '1.27', '1.26', '1.25', '1.24', '1.23'] + requirements: [] + incompatibilities: [] + summary: null + chart_version: 1.1.3 + images: ['nginx/nginx-ingress:3.4.3'] diff --git a/utils/compatibility/scrapers/nginx-ingress-controller.py b/utils/compatibility/scrapers/nginx-ingress-controller.py new file mode 100644 index 0000000000..b1e65ea50e --- /dev/null +++ b/utils/compatibility/scrapers/nginx-ingress-controller.py @@ -0,0 +1,182 @@ +import json +import re +from collections import OrderedDict +from functools import lru_cache + +import requests +import yaml +from packaging.version import Version + +from utils import ( + ensure_keys, + expand_kube_versions, + fetch_page, + print_error, + print_success, + read_yaml, + reduce_versions, + update_versions_data, + write_yaml, +) + + +APP_NAME = "nginx-ingress-controller" +COMPATIBILITY_URL = "https://raw.githubusercontent.com/nginx/documentation/main/content/includes/nic/compatibility-tables/nic-k8s.md" +LATEST_RELEASE_URL = "https://api.github.com/repos/nginx/kubernetes-ingress/releases/latest" +CHART_YAML_URL = "https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{version}/charts/nginx-ingress/Chart.yaml" +CHART_VALUES_URL = "https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{version}/charts/nginx-ingress/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(value): + return value.strip().strip("*").strip() + + +def _latest_release_versions(): + release_content = fetch_page(LATEST_RELEASE_URL) + if not release_content: + return None, None + + try: + release = json.loads(_decode(release_content)) + except json.JSONDecodeError as exc: + print_error(f"Failed to parse NGINX Ingress Controller release metadata: {exc}") + return None, None + + version = str(release.get("tag_name", "")).lstrip("v") + if not version: + print_error("No NGINX Ingress Controller latest release tag found.") + return None, None + + chart_content = fetch_page(CHART_YAML_URL.format(version=version)) + if not chart_content: + return version, None + + try: + chart = yaml.safe_load(_decode(chart_content)) + except yaml.YAMLError as exc: + print_error(f"Failed to parse NGINX Ingress Controller Chart.yaml: {exc}") + return version, None + + return version, str(chart.get("version", "")) + + +@lru_cache(maxsize=None) +def _fetch_yaml(url): + try: + response = requests.get(url, timeout=10) + except requests.RequestException: + return {} + if response.status_code != 200: + return {} + + try: + parsed = yaml.safe_load(response.text) + except yaml.YAMLError as exc: + print_error(f"Failed to parse NGINX Ingress Controller chart metadata: {exc}") + return {} + + return parsed if isinstance(parsed, dict) else {} + + +def _resolve_shortcodes(value, latest_version, latest_chart_version): + return ( + value.replace("{{< nic-version >}}", latest_version) + .replace("{{< nic-helm-version >}}", latest_chart_version) + .replace("{{< nic-operator-version >}}", "") + ) + + +def _kube_versions(value): + match = re.search(r"(\d+\.\d+)\s*(?:-|–|—)\s*(\d+\.\d+)", value) + if match: + return expand_kube_versions(match.group(1), match.group(2)) + + return [match.group(1) for match in re.finditer(r"(\d+\.\d+)", value)] + + +def _default_image(app_version): + chart = _fetch_yaml(CHART_YAML_URL.format(version=app_version)) + values = _fetch_yaml(CHART_VALUES_URL.format(version=app_version)) + + controller = values.get("controller", {}) if isinstance(values, dict) else {} + image = controller.get("image", {}) if isinstance(controller, dict) else {} + repository = image.get("repository") or "nginx/nginx-ingress" + tag = image.get("tag") or chart.get("appVersion") or app_version + + return f"{repository}:{tag}" + + +def _table_rows(content, latest_version, latest_chart_version): + rows = [] + + for line in _decode(content).splitlines(): + stripped = line.strip() + if not stripped.startswith("|"): + continue + if set(stripped.replace("|", "").strip()) <= {"-"}: + continue + + columns = [_clean_cell(column) for column in stripped.strip("|").split("|")] + if len(columns) < 3 or columns[0] == "NIC version": + continue + + app_version = _resolve_shortcodes( + columns[0], latest_version, latest_chart_version + ) + chart_version = _resolve_shortcodes( + columns[2], latest_version, latest_chart_version + ) + kube_versions = _kube_versions(columns[1]) + + if not re.fullmatch(r"\d+\.\d+\.\d+", app_version): + continue + if not re.fullmatch(r"\d+\.\d+\.\d+", chart_version): + continue + if not kube_versions: + continue + + rows.append( + OrderedDict( + [ + ("version", app_version), + ("kube", sorted(kube_versions, key=Version, reverse=True)), + ("requirements", []), + ("incompatibilities", []), + ("chart_version", chart_version), + ("images", [_default_image(app_version)]), + ] + ) + ) + + return sorted(rows, key=lambda row: Version(row["version"]), reverse=True) + + +def scrape(): + content = fetch_page(COMPATIBILITY_URL) + if not content: + print_error("Failed to fetch NGINX Ingress Controller compatibility table.") + return + + latest_version, latest_chart_version = _latest_release_versions() + if not latest_version or not latest_chart_version: + print_error("Failed to resolve NGINX Ingress Controller latest chart version.") + return + + rows = _table_rows(content, latest_version, latest_chart_version) + if not rows: + print_error("No NGINX Ingress Controller compatibility rows found.") + return + + data = read_yaml(TARGET_FILE) or {} + update_versions_data(data, [ensure_keys(row) for row in rows]) + data["versions"] = reduce_versions(data["versions"]) + + if write_yaml(TARGET_FILE, data): + print_success(f"Updated compatibility info table: {TARGET_FILE}") + else: + print_error(f"Failed to update compatibility info for {TARGET_FILE}")