diff --git a/kubernetes/conformance/install-wso2-gateway.sh b/kubernetes/conformance/install-wso2-gateway.sh new file mode 100755 index 0000000000..5a57082e2a --- /dev/null +++ b/kubernetes/conformance/install-wso2-gateway.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# Install everything the WSO2 API Platform gateway needs on the conformance +# cluster: cert-manager (required by the gateway Helm chart), the gateway operator, +# and the GatewayClass the suite targets. The operator chart installs the bundled +# standard-channel Gateway API CRDs (v1.5.1) itself via gatewayApi.installStandardCRDs. +# +# Run AFTER kind/setup-kind.sh. Assumes kubectl context points at the conformance +# cluster (setup-kind.sh switches to it). +# +# Overridable via env: +# OPERATOR_CHART path/ref to the operator Helm chart +# OPERATOR_NS namespace for the operator (default: gateway-system) +# ----------------------------------------------------------------------------- +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +OPERATOR_CHART="${OPERATOR_CHART:-${REPO_ROOT}/kubernetes/helm/operator-helm-chart}" +OPERATOR_NS="${OPERATOR_NS:-gateway-system}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- 1. cert-manager (the gateway chart creates Certificate/Issuer) --------- +echo ">> Installing cert-manager" +helm repo add jetstack https://charts.jetstack.io --force-update +helm repo update +helm upgrade --install cert-manager jetstack/cert-manager \ + --namespace cert-manager --create-namespace \ + --set crds.enabled=true \ + --wait +kubectl wait --namespace cert-manager \ + --for=condition=available deployment --all --timeout=180s + +# --- 2. Gateway operator (installs the bundled Gateway API CRDs too) -------- +echo ">> Installing the gateway operator from ${OPERATOR_CHART}" +helm upgrade --install gateway-operator "${OPERATOR_CHART}" \ + --namespace "${OPERATOR_NS}" --create-namespace \ + --set image.repository=ghcr.io/wso2/api-platform/gateway-operator \ + --set image.tag=0.8.1-SNAPSHOT \ + --set image.pullPolicy=IfNotPresent \ + --set gateway.values.gateway.controller.image.repository=ghcr.io/wso2/api-platform/gateway-controller \ + --set gateway.values.gateway.controller.image.tag=1.2.0-SNAPSHOT \ + --set gateway.values.gateway.controller.image.pullPolicy=IfNotPresent \ + --set gateway.values.gateway.gatewayRuntime.image.repository=ghcr.io/wso2/api-platform/gateway-runtime \ + --set gateway.values.gateway.gatewayRuntime.image.tag=1.2.0-SNAPSHOT \ + --set gateway.values.gateway.gatewayRuntime.image.pullPolicy=IfNotPresent \ + --set gateway.values.gateway.config.controller.storage.type=memory \ + --set gateway.values.gateway.controller.storage.type=sqlite \ + --set gateway.values.gateway.controller.persistence.enabled=false \ + --set gateway.values.gateway.gatewayRuntime.deployment.securityContext.runAsUser=0 \ + --set reconciliation.maxConcurrentReconciles=15 \ + --wait +kubectl rollout status --namespace "${OPERATOR_NS}" deployment --timeout=240s + +# --- 3. GatewayClass -------------------------------------------------------- +echo ">> Creating the GatewayClass" +kubectl apply -f "${SCRIPT_DIR}/manifests/gatewayclass.yaml" +echo ">> Waiting for GatewayClass to be Accepted" +kubectl wait --for=condition=Accepted gatewayclass/wso2-api-platform --timeout=120s || \ + echo " (GatewayClass not yet Accepted — check operator logs in ns ${OPERATOR_NS})" + +echo ">> Install complete. Run ./run-conformance.sh next." diff --git a/kubernetes/conformance/kind/cluster.yaml b/kubernetes/conformance/kind/cluster.yaml new file mode 100644 index 0000000000..c90ec65734 --- /dev/null +++ b/kubernetes/conformance/kind/cluster.yaml @@ -0,0 +1,15 @@ +# Single-node KinD cluster used to run the Gateway API conformance suite against +# the WSO2 API Platform gateway. Intentionally vanilla — the only moving parts are +# the pinned node image (Kubernetes version) and MetalLB (added by setup-kind.sh), +# which gives the operator-provisioned gateway-runtime LoadBalancer Service a +# routable address the conformance suite can dial. Mirrors the layout the other +# Envoy-based implementations (kgateway, gloo) use for their published reports. +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +# One node, control-plane + worker combined. Conformance does not need multiple +# nodes, and a single node keeps the MetalLB L2 address pool trivial to reason about. +nodes: + - role: control-plane +# Defaults everywhere else: kindnet CNI, iptables kube-proxy, IPv4, default +# pod/service subnets. No extraPortMappings — the suite reaches the gateway via the +# MetalLB-assigned LoadBalancer IP on the "kind" docker bridge, not via host ports. diff --git a/kubernetes/conformance/kind/setup-colima.sh b/kubernetes/conformance/kind/setup-colima.sh new file mode 100755 index 0000000000..da76c8b21a --- /dev/null +++ b/kubernetes/conformance/kind/setup-colima.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# macOS / Colima helper: bring up a Colima VM with a host-reachable IP, create the +# KinD + MetalLB conformance cluster, and route the kind bridge subnet through the +# VM so the conformance suite (running on the host) can dial the gateway's +# LoadBalancer IP — i.e. Linux-equivalent reachability on a Mac. +# +# This wraps setup-kind.sh; on Linux you don't need any of this. +# +# Usage: +# ./setup-colima.sh # start Colima + cluster + route + reachability gate +# ./setup-colima.sh --delete # delete route, cluster, and stop Colima +# +# Overridable via env (passed through to setup-kind.sh too): +# COLIMA_CPU (6) COLIMA_MEMORY (10) +# CLUSTER_NAME (wso2-conformance) CLUSTER_NODE_VERSION METALLB_VERSION +# ----------------------------------------------------------------------------- +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +COLIMA_CPU="${COLIMA_CPU:-6}" +COLIMA_MEMORY="${COLIMA_MEMORY:-10}" +COLIMA_DISK="${COLIMA_DISK:-60}" +CLUSTER_NAME="${CLUSTER_NAME:-wso2-conformance}" + +need() { command -v "$1" >/dev/null 2>&1 || { echo "!! missing required tool: $1" >&2; exit 1; }; } + +vm_ip() { colima ls --json 2>/dev/null | jq -r 'select(.address != null and .address != "") | .address' | head -n1; } + +kind_subnet() { + docker network inspect kind 2>/dev/null \ + | jq -r '.[].IPAM.Config[].Subnet | select(contains(":") | not)' | head -n1 +} + +# --------------------------------------------------------------------------- # +# Teardown +# --------------------------------------------------------------------------- # +if [[ "${1:-}" == "--delete" ]]; then + SUBNET="$(kind_subnet || true)" + if [[ -n "${SUBNET}" ]] && netstat -rn -f inet | grep -q "${SUBNET%%/*}"; then + echo ">> Removing host route for ${SUBNET}" + sudo route -n delete -net "${SUBNET}" >/dev/null 2>&1 || true + fi + echo ">> Deleting KinD cluster '${CLUSTER_NAME}'" + CLUSTER_NAME="${CLUSTER_NAME}" "${SCRIPT_DIR}/setup-kind.sh" --delete || true + echo ">> Stopping Colima" + colima stop || true + echo ">> Done. (Run 'colima delete' to remove the VM entirely.)" + exit 0 +fi + +# --------------------------------------------------------------------------- # +# 0. Dependencies +# --------------------------------------------------------------------------- # +# need colima; need jq; need kind; need kubectl +# if ! brew list socket_vmnet >/dev/null 2>&1; then +# echo "!! socket_vmnet is required for 'colima start --network-address'." >&2 +# echo " Install it with: brew install socket_vmnet" >&2 +# exit 1 +# fi + + +# ---- 1. Colima VM with a reachable IP -------------------------------------- +if colima status >/dev/null 2>&1; then + echo ">> Colima already running, reusing it" + if [[ -z "$(vm_ip)" ]]; then + echo "!! Colima is running WITHOUT a network address. Restart it so the host can" >&2 + echo " reach the cluster: colima stop && colima start --network-address ..." >&2 + exit 1 + fi +else + echo ">> Starting Colima (${COLIMA_CPU} CPU / ${COLIMA_MEMORY} GB, --network-address)" + colima start \ + --cpu "${COLIMA_CPU}" --memory "${COLIMA_MEMORY}" \ + --network-address +fi +docker context use colima >/dev/null +need docker + +VMIP="$(vm_ip)" +[[ -n "${VMIP}" ]] || { echo "!! could not determine Colima VM IP from 'colima ls --json'" >&2; exit 1; } +echo ">> Colima VM IP: ${VMIP}" + +# --- 2. KinD + MetalLB cluster (delegated to the shared script) ---------------- +# Skip setup-kind.sh's own reachability check: on macOS the host can't reach the LB +# IP until we add the route + VM forwarding below. Otherwise the in-kind check would +# fail prematurely. +SKIP_REACHABILITY_CHECK=1 CLUSTER_NAME="${CLUSTER_NAME}" "${SCRIPT_DIR}/setup-kind.sh" + + +# ---- 3. Host route to the kind bridge subnet via the VM ---------------------- +SUBNET="$(kind_subnet)" +[[ -n "${SUBNET}" ]] || { echo "!! could not determine 'kind' docker network subnet" >&2; exit 1; } +echo ">> Adding host route: ${SUBNET} -> ${VMIP} (sudo)" +sudo route -n delete -net "${SUBNET}" >/dev/null 2>&1 || true +sudo route -n add -net "${SUBNET}" "${VMIP}" + +# ----- 4. Enable forwarding through the VM (proactively, not as a fallback) --- +# Routing host -> LB IP sends packets to the VM, which must forward them onto its +# kind bridge. This essentially always requires ip_forward + a FORWARD ACCEPT for +# the bridge subnet, so apply it up front. Use -C (check) to stay idempotent. +echo ">> Enabling packet forwarding for ${SUBNET} inside the Colima VM" +colima ssh -- sudo sysctl -w net.ipv4.ip_forward=1 >/dev/null +colima ssh -- sudo iptables -C FORWARD -d "${SUBNET}" -j ACCEPT 2>/dev/null \ + || colima ssh -- sudo iptables -I FORWARD -d "${SUBNET}" -j ACCEPT +colima ssh -- sudo iptables -C FORWARD -s "${SUBNET}" -j ACCEPT 2>/dev/null \ + || colima ssh -- sudo iptables -I FORWARD -s "${SUBNET}" -j ACCEPT + + +# ---- 5. Reachability gate (host -> MetalLB LoadBalancer IP) ---------------- +# Create the smoke workload ONCE, wait for the backend pod to be Ready (a fresh VM +# has to pull the image first; an LB IP is assigned long before the pod is up), then +# retry the probe so ARP/endpoints have time to settle. +echo ">> Checking host -> LoadBalancer reachability" +kubectl delete svc lb-smoke --ignore-not-found >/dev/null 2>&1 || true +kubectl delete deployment lb-smoke --ignore-not-found >/dev/null 2>&1 || true +kubectl create deployment lb-smoke \ + --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080 >/dev/null +kubectl expose deployment lb-smoke --type=LoadBalancer --port=80 --target-port=8080 >/dev/null + +echo ">> Waiting for the smoke backend pod to be Ready" +kubectl rollout status deployment/lb-smoke --timeout=180s + +IP="" +for _ in $(seq 1 30); do + IP="$(kubectl get svc lb-smoke -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true)" + [[ -n "${IP}" ]] && break + sleep 2 +done +echo ">> LoadBalancer IP: ${IP:-}" + +CODE="000" +if [[ -n "${IP}" ]]; then + for _ in $(seq 1 15); do + CODE="$(curl -s -m 5 -o /dev/null -w '%{http_code}' "http://${IP}/" || true)" + [[ "${CODE}" == "200" ]] && break + sleep 3 + done +fi + +kubectl delete svc lb-smoke --ignore-not-found >/dev/null 2>&1 || true +kubectl delete deployment lb-smoke --ignore-not-found >/dev/null 2>&1 || true + +if [[ "${CODE}" == "200" ]]; then + echo ">> host -> LoadBalancer = 200 ✓ The conformance suite can reach the gateway from the host." +else + echo "!! host -> LoadBalancer failing (got ${CODE})." >&2 + echo " The cluster is up, but the host can't route to ${SUBNET} via ${VMIP}. Check:" >&2 + echo " - route present: netstat -rn | grep ${SUBNET%%/*}" >&2 + echo " - VM reachable: ping -c1 ${VMIP}" >&2 + echo " - forwarding on: colima ssh -- sudo sysctl net.ipv4.ip_forward" >&2 + echo " You can still run the suite in-cluster." >&2 + exit 1 +fi + +echo ">> Done. Next: ./install-wso2-gateway.sh && ./run-conformance.sh" +echo " (route is not persistent — re-run this script after a reboot or 'colima restart')" diff --git a/kubernetes/conformance/kind/setup-kind.sh b/kubernetes/conformance/kind/setup-kind.sh new file mode 100755 index 0000000000..e7242bc871 --- /dev/null +++ b/kubernetes/conformance/kind/setup-kind.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# Create a KinD cluster and install MetalLB for Gateway API conformance, then +# validate that LoadBalancer traffic routing actually works. +# +# Usage: +# ./setup-kind.sh # create cluster "wso2-conformance" + MetalLB + validate +# CLUSTER_NAME=foo ./setup-kind.sh +# ./setup-kind.sh --delete # tear the cluster down +# +# Env: +# CLUSTER_NAME cluster name (default: wso2-conformance) +# CLUSTER_NODE_VERSION kindest/node image (pinned by digest) +# METALLB_VERSION MetalLB release (default: v0.14.8) +# SKIP_REACHABILITY_CHECK=1 skip the host->LoadBalancer validation. On a bare +# Linux host you normally leave this unset. +# ----------------------------------------------------------------------------- +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +CLUSTER_NAME="${CLUSTER_NAME:-wso2-conformance}" +# Pin the Kubernetes version by digest for reproducibility. Override CLUSTER_NODE_VERSION +# to certify against a different Kubernetes release. +CLUSTER_NODE_VERSION="${CLUSTER_NODE_VERSION:-v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a}" +METALLB_VERSION="${METALLB_VERSION:-v0.14.8}" + +need() { command -v "$1" >/dev/null 2>&1 || { echo "!! missing required tool: $1" >&2; exit 1; }; } + +if [[ "${1:-}" == "--delete" ]]; then + need kind + echo ">> Deleting KinD cluster '${CLUSTER_NAME}'" + kind delete cluster --name "${CLUSTER_NAME}" + exit 0 +fi + +# --- 0. Dependencies -------------------------------------------------------- +for t in kind kubectl docker jq curl; do need "$t"; done + +# --- 1. Cluster ------------------------------------------------------------- +if kind get clusters 2>/dev/null | grep -qx "${CLUSTER_NAME}"; then + echo ">> KinD cluster '${CLUSTER_NAME}' already exists, reusing it" +else + echo ">> Creating KinD cluster '${CLUSTER_NAME}' (node ${CLUSTER_NODE_VERSION})" + kind create cluster \ + --name "${CLUSTER_NAME}" \ + --image "kindest/node:${CLUSTER_NODE_VERSION}" \ + --config "${SCRIPT_DIR}/cluster.yaml" +fi +kubectl config use-context "kind-${CLUSTER_NAME}" + +# --- 2. MetalLB ------------------------------------------------------------- +echo ">> Installing MetalLB ${METALLB_VERSION}" +kubectl apply -f "https://raw.githubusercontent.com/metallb/metallb/${METALLB_VERSION}/config/manifests/metallb-native.yaml" +echo ">> Waiting for MetalLB controller to be ready" +kubectl wait --namespace metallb-system \ + --for=condition=available deployment/controller \ + --timeout=180s +# The speaker is a DaemonSet; wait for its pods to be Ready before advertising. +kubectl rollout status --namespace metallb-system daemonset/speaker --timeout=180s +# The controller serves a validating webhook for IPAddressPool/L2Advertisement; wait +# for its endpoints so the apply below doesn't race the webhook ("connection refused"). +echo ">> Waiting for the MetalLB webhook endpoints" +for _ in $(seq 1 30); do + if [[ -n "$(kubectl get endpoints metallb-webhook-service -n metallb-system \ + -o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null)" ]]; then + break + fi + sleep 2 +done + +# --- 3. Address pool derived from the "kind" docker network ----------------- +# Take the IPv4 subnet KinD's docker bridge uses and reserve a high band of it +# (x.x.255.200 - x.x.255.250) that won't collide with node container IPs. +SUBNET="$(docker network inspect kind \ + | jq -r '.[].IPAM.Config[].Subnet | select(contains(":") | not)' \ + | head -n1 | cut -d '.' -f1,2)" +if [[ -z "${SUBNET}" ]]; then + echo "!! Could not determine the 'kind' docker network subnet" >&2 + exit 1 +fi +ADDR_RANGE="${SUBNET}.255.200-${SUBNET}.255.250" +echo ">> MetalLB address pool: ${ADDR_RANGE}" + +apply_metallb_pool() { + kubectl apply -f - <&2 + exit 1 + fi + echo " (MetalLB webhook not ready yet, retrying ${attempt}/15)" + sleep 4 +done + +echo ">> Cluster '${CLUSTER_NAME}' is ready with MetalLB serving ${ADDR_RANGE}." + +# --- 4. Validate LoadBalancer routing (host -> MetalLB LB IP) --------------- +# Confirms MetalLB actually assigns a LB IP and that traffic routes to a backend. +# Skipped when SKIP_REACHABILITY_CHECK=1 (e.g. setup-colima.sh runs this after it +# adds its host route, so the in-line check here would fire prematurely). +if [[ "${SKIP_REACHABILITY_CHECK:-}" == "1" ]]; then + echo ">> Skipping LoadBalancer reachability check (SKIP_REACHABILITY_CHECK=1)." +else + echo ">> Validating LoadBalancer traffic routing" + kubectl delete svc lb-smoke --ignore-not-found >/dev/null 2>&1 || true + kubectl delete deployment lb-smoke --ignore-not-found >/dev/null 2>&1 || true + kubectl create deployment lb-smoke \ + --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080 >/dev/null + kubectl expose deployment lb-smoke --type=LoadBalancer --port=80 --target-port=8080 >/dev/null + + echo ">> Waiting for the smoke backend pod to be Ready" + kubectl rollout status deployment/lb-smoke --timeout=180s + + IP="" + for _ in $(seq 1 30); do + IP="$(kubectl get svc lb-smoke -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true)" + [[ -n "${IP}" ]] && break + sleep 2 + done + echo ">> LoadBalancer IP: ${IP:-}" + + CODE="000" + if [[ -n "${IP}" ]]; then + for _ in $(seq 1 15); do + CODE="$(curl -s -m 5 -o /dev/null -w '%{http_code}' "http://${IP}/" || true)" + [[ "${CODE}" == "200" ]] && break + sleep 3 + done + fi + + kubectl delete svc lb-smoke --ignore-not-found >/dev/null 2>&1 || true + kubectl delete deployment lb-smoke --ignore-not-found >/dev/null 2>&1 || true + + if [[ "${IP}" == "" ]]; then + echo "!! MetalLB did not assign a LoadBalancer IP — check the speaker pods and the address pool." >&2 + exit 1 + fi + if [[ "${CODE}" == "200" ]]; then + echo ">> host -> LoadBalancer = 200 ✓ LoadBalancer routing works; the suite can reach the gateway." + else + echo "!! host -> LoadBalancer failing (got ${CODE})." >&2 + echo " MetalLB assigned ${IP} but the host could not reach it." >&2 + echo " On Linux the kind bridge is host-routable, so check MetalLB speaker logs / firewall." >&2 + echo " On macOS (Docker/Rancher/Colima) the bridge is NOT host-routable — use ./setup-colima.sh," >&2 + echo " or run the conformance suite from inside the cluster." >&2 + exit 1 + fi +fi + +echo ">> Done." +echo " Next: install the gateway operator and the GatewayClass (./install-wso2-gateway.sh; see README.md)." diff --git a/kubernetes/conformance/manifests/gatewayclass.yaml b/kubernetes/conformance/manifests/gatewayclass.yaml new file mode 100644 index 0000000000..de3845c3e4 --- /dev/null +++ b/kubernetes/conformance/manifests/gatewayclass.yaml @@ -0,0 +1,9 @@ +# GatewayClass the conformance suite is pointed at via `-gateway-class wso2-api-platform`. +# Its controllerName must match the operator's PlatformGatewayControllerName so the +# operator reconciles Gateways of this class and provisions the gateway-runtime. +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: wso2-api-platform +spec: + controllerName: gateway.api-platform.wso2.com/gateway-operator diff --git a/kubernetes/conformance/run-conformance.sh b/kubernetes/conformance/run-conformance.sh new file mode 100755 index 0000000000..7ef6338782 --- /dev/null +++ b/kubernetes/conformance/run-conformance.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# Run the Gateway API conformance suite against the WSO2 API Platform gateway +# and write the ConformanceReport. +# +# Run AFTER install-wso2-gateway.sh, with kubectl pointed at the conformance +# cluster. The suite drives the cluster via the current kube context: it creates +# Gateways of the GatewayClass below, applies HTTPRoutes + backends, then sends +# real requests to the MetalLB-assigned Gateway address. +# +# The suite is consumed as a Go module dependency (conformance-report/runner), +# NOT from a clone of the kubernetes-sigs/gateway-api repo — its test manifests are +# embedded in the module, so `go test` resolves everything from the module cache. +# +# Overridable via env: +# GATEWAY_CLASS GatewayClass name (default: wso2-api-platform) +# PROFILE conformance profile (default: GATEWAY-HTTP) +# SUPPORTED_FEATURES comma-separated features (default: Gateway,HTTPRoute) +# IMPL_VERSION implementation version string (default: 1.1.0) +# REPORT_OUT report output path +# ----------------------------------------------------------------------------- +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUNNER_DIR="${SCRIPT_DIR}/runner" + +GATEWAY_CLASS="${GATEWAY_CLASS:-wso2-api-platform}" +PROFILE="${PROFILE:-GATEWAY-HTTP}" +# Keep this on ONE line: a line break (even with a trailing backslash) leaves the +# continuation's leading whitespace inside the value, corrupting the feature name +# right after each break so that feature silently fails to register. +SUPPORTED_FEATURES="${SUPPORTED_FEATURES:-Gateway,HTTPRoute,HTTPRouteSchemeRedirect,HTTPRoutePortRedirect,HTTPRoute303RedirectStatusCode,HTTPRoute307RedirectStatusCode,HTTPRoute308RedirectStatusCode}" +IMPL_VERSION="${IMPL_VERSION:-1.2.0-milestone}" +REPORT_OUT="${REPORT_OUT:-${SCRIPT_DIR}/wso2-api-platform-${IMPL_VERSION}-report.yaml}" + +echo ">> Running conformance: profile=${PROFILE} gateway-class=${GATEWAY_CLASS}" +echo ">> Report will be written to ${REPORT_OUT}" + +cd "${RUNNER_DIR}" +go test -tags conformance -v -run TestConformance -timeout 20m -args \ + -gateway-class="${GATEWAY_CLASS}" \ + -supported-features="${SUPPORTED_FEATURES}" \ + -conformance-profiles="${PROFILE}" \ + -cleanup-base-resources=true \ + -organization=WSO2 \ + -project=api-platform-gateway \ + -url=https://github.com/wso2/api-platform \ + -version="${IMPL_VERSION}" \ + -contact=https://github.com/wso2/api-platform/issues \ + -report-output="${REPORT_OUT}" \ + -allow-crds-mismatch=true + +echo ">> Conformance run finished. Report: ${REPORT_OUT}" diff --git a/kubernetes/conformance/runner/conformance_test.go b/kubernetes/conformance/runner/conformance_test.go new file mode 100644 index 0000000000..6f2ae61948 --- /dev/null +++ b/kubernetes/conformance/runner/conformance_test.go @@ -0,0 +1,72 @@ +//go:build conformance + +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package conformance runs the upstream Gateway API conformance suite against the +// WSO2 API Platform gateway. The suite (and its test manifests, which are embedded +// in the module via //go:embed) is consumed as a Go module dependency, so no clone +// of the kubernetes-sigs/gateway-api repository is needed — `go test` resolves +// everything from the module cache. This mirrors how other implementations +// (kgateway, gloo, envoy-gateway) wire up their conformance runs. +// +// Build-tagged with `conformance` so it is excluded from normal `go test ./...` +// sweeps and only runs when explicitly requested (see run-conformance.sh). +package conformance_test + +import ( + "os" + "strconv" + "testing" + "time" + + "sigs.k8s.io/gateway-api/conformance" +) + +// envDuration reads a duration-in-seconds override from the environment, falling +// back to def when unset or unparseable. Lets the run script tune timing without +// editing code (see run-conformance.sh). +func envDuration(name string, def time.Duration) time.Duration { + if v := os.Getenv(name); v != "" { + if secs, err := strconv.Atoi(v); err == nil { + return time.Duration(secs) * time.Second + } + } + return def +} + +// TestConformance is the entrypoint. Options (gateway class, profile, report path, +// implementation metadata) are supplied as command-line flags after `-args`. +// +// We start from DefaultOptions (which parses those flags) and then relax the suite's +// timing. Two knobs help: +// - TestIsolation: a quiet gap between test cases so resources from the previous +// test settle/clean up before the next one provisions (this is the "delay +// between tests"). +// - MaxTimeToConsistency / NamespacesMustBeReady: give each test longer to reach a +// consistent routing state before it declares failure. +// +// All are overridable via env (seconds); defaults are conservative for local runs. +// On a stable cluster you can set them to 0 / the upstream defaults. +func TestConformance(t *testing.T) { + opts := conformance.DefaultOptions(t) + + opts.TimeoutConfig.TestIsolation = envDuration("CONFORMANCE_TEST_ISOLATION", 5*time.Second) + opts.TimeoutConfig.MaxTimeToConsistency = envDuration("CONFORMANCE_MAX_CONSISTENCY", 60*time.Second) + opts.TimeoutConfig.NamespacesMustBeReady = envDuration("CONFORMANCE_NAMESPACES_READY", 5*time.Minute) + + conformance.RunConformanceWithOptions(t, opts) +} diff --git a/kubernetes/conformance/runner/doc.go b/kubernetes/conformance/runner/doc.go new file mode 100644 index 0000000000..69cf621c4e --- /dev/null +++ b/kubernetes/conformance/runner/doc.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package conformance hosts the Gateway API conformance runner for the WSO2 API +// Platform gateway. The actual entrypoint lives in conformance_test.go, which is +// guarded by the `conformance` build tag and invoked via run-conformance.sh. This +// file exists so the package is non-empty even when that tag is absent. +package conformance diff --git a/kubernetes/conformance/runner/go.mod b/kubernetes/conformance/runner/go.mod new file mode 100644 index 0000000000..fd3ede94be --- /dev/null +++ b/kubernetes/conformance/runner/go.mod @@ -0,0 +1,65 @@ +module github.com/wso2/api-platform/conformance-report/runner + +go 1.26.2 + +require sigs.k8s.io/gateway-api/conformance v1.5.1 + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/miekg/dns v1.1.72 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.41.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/grpc v1.78.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.35.1 // indirect + k8s.io/apiextensions-apiserver v0.35.1 // indirect + k8s.io/apimachinery v0.35.1 // indirect + k8s.io/client-go v0.35.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect + sigs.k8s.io/controller-runtime v0.23.1 // indirect + sigs.k8s.io/gateway-api v1.5.1 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/kubernetes/conformance/runner/go.sum b/kubernetes/conformance/runner/go.sum new file mode 100644 index 0000000000..b8936d7cac --- /dev/null +++ b/kubernetes/conformance/runner/go.sum @@ -0,0 +1,181 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc= +github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= +k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= +k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= +k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= +k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= +k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= +k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= +k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= +sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/gateway-api v1.5.1 h1:RqVRIlkhLhUO8wOHKTLnTJA6o/1un4po4/6M1nRzdd0= +sigs.k8s.io/gateway-api v1.5.1/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o= +sigs.k8s.io/gateway-api/conformance v1.5.1 h1:5eruSMKcwKnkX42PFek8oO6BgPNBD5FbWbTcRV76KIw= +sigs.k8s.io/gateway-api/conformance v1.5.1/go.mod h1:mcvYR0Zll1i5hmcKn+jNbWdZTBls6s5GU+FPUFIceXw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=