diff --git a/hack/install-ate.sh b/hack/install-ate.sh index d7eafb457f..4f51f46172 100755 --- a/hack/install-ate.sh +++ b/hack/install-ate.sh @@ -396,10 +396,34 @@ deploy_ate_apiserver() { ensure_apiserver_prerequisites - run_ko apply -f manifests/ate-install/ate-api-server.yaml + component_manifests ate-api-server | run_ko resolve -f - | run_kubectl apply -f - run_kubectl rollout status deployment/ate-api-server -n ate-system --timeout=120s } +# Kind overlay vs base manifests is decided ONLY here; hack/verify/kind-overlays.sh +# sources this to assert kind deploys can't bypass the overlay and silently +# revert its env patches (e.g. pointing OTel exports at the GKE-only collector). +component_manifests() { + local component="$1" + if [[ "${ATE_INSTALL_KIND:-false}" == "true" ]]; then + kubectl kustomize "manifests/ate-install/kind/${component}" --load-restrictor LoadRestrictionsNone + return + fi + case "${component}" in + atenet) + cat manifests/ate-install/atenet-router.yaml + printf '\n---\n' + cat manifests/ate-install/atenet-dns.yaml + ;; + ate-api-server) cat manifests/ate-install/ate-api-server.yaml ;; + atelet) cat manifests/ate-install/atelet.yaml ;; + *) + echo "component_manifests: unknown component ${component}" >&2 + return 1 + ;; + esac +} + deploy_atelet() { log_step "deploy_atelet" ensure_crds @@ -408,15 +432,7 @@ deploy_atelet() { run_kubectl apply -f manifests/ate-install/ate-system-namespace.yaml \ && run_kubectl wait --for=jsonpath='{.status.phase}'=Active namespace/ate-system --timeout=60s - local manifest="" - if [[ "${ATE_INSTALL_KIND:-false}" == "true" ]]; then - # Use Kustomize to build and resolve the atelet DaemonSet patch - manifest=$(kubectl kustomize manifests/ate-install/kind/atelet --load-restrictor LoadRestrictionsNone | run_ko resolve -f -) - else - # Use base manifest for GKE - manifest=$(run_ko resolve -f manifests/ate-install/atelet.yaml) - fi - echo "${manifest}" | run_kubectl apply -f - + component_manifests atelet | run_ko resolve -f - | run_kubectl apply -f - run_kubectl rollout status daemonset/atelet -n ate-system --timeout=120s } @@ -428,11 +444,15 @@ deploy_atenet() { run_kubectl apply -f manifests/ate-install/ate-system-namespace.yaml \ && run_kubectl wait --for=jsonpath='{.status.phase}'=Active namespace/ate-system --timeout=60s - local router_manifest="" - router_manifest="$(render_atenet_router_manifest)" - echo "${router_manifest}" | run_kubectl apply -f - + if [[ "$(atenet_router)" == "agentgateway" ]]; then + local router_manifest="" + router_manifest="$(render_atenet_router_manifest)" + echo "${router_manifest}" | run_kubectl apply -f - - run_ko apply -f manifests/ate-install/atenet-dns.yaml + run_ko apply -f manifests/ate-install/atenet-dns.yaml + else + component_manifests atenet | run_ko resolve -f - | run_kubectl apply -f - + fi run_kubectl rollout status deployment/atenet-router -n ate-system --timeout=120s # The Deployment in atenet-dns.yaml is named "dns"; every other resource in # that file is "atenet-dns". Waiting on the filename rather than the actual @@ -577,6 +597,11 @@ delete_all() { delete_ate_system } +# Library mode: let hack/verify/kind-overlays.sh source the functions without dispatching. +if [[ -n "${ATE_INSTALL_LIB_ONLY:-}" ]]; then + return 0 +fi + if [ "$#" -eq 0 ]; then usage exit 1 diff --git a/hack/verify/kind-overlays.sh b/hack/verify/kind-overlays.sh new file mode 100755 index 0000000000..82b2a76419 --- /dev/null +++ b/hack/verify/kind-overlays.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +# Copyright 2026 Google LLC +# +# 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. + +# Guards against partial-deploy drift: kind-mode component deploys must emit exactly +# the component overlay render, component and full-overlay renders must match, and +# each container must carry its OTel env — value-checked per container because a +# renamed container turns a strategic-merge patch into a silent ghost sibling. + +set -o errexit -o nounset -o pipefail + +ROOT="$(git rev-parse --show-toplevel)" +cd "${ROOT}" + +if ! command -v kubectl >/dev/null 2>&1; then + echo "FAIL: kubectl is required for kind overlay verification" >&2 + exit 1 +fi + +OUT="$(mktemp -d)" +trap 'rm -rf "${OUT}"' EXIT + +kubectl kustomize manifests/ate-install/kind --load-restrictor LoadRestrictionsNone > "${OUT}/root.yaml" +for c in atenet ate-api-server atelet; do + kubectl kustomize "manifests/ate-install/kind/${c}" --load-restrictor LoadRestrictionsNone > "${OUT}/${c}.yaml" + ( + # shellcheck disable=SC2030 # subshell-local env is the point: lib-mode source must not leak + export ATE_INSTALL_LIB_ONLY=1 NO_DEV_ENV=true KUBECTL_CONTEXT=verify-only + # shellcheck source=/dev/null + source hack/install-ate.sh + ATE_INSTALL_KIND=true component_manifests "${c}" + ) > "${OUT}/${c}.deploy.yaml" + if ! diff -u "${OUT}/${c}.yaml" "${OUT}/${c}.deploy.yaml" > "${OUT}/${c}.routing.diff"; then + echo "FAIL: install-ate.sh component_manifests ${c} (kind mode) does not emit the ${c} overlay render:" >&2 + head -20 "${OUT}/${c}.routing.diff" >&2 + exit 1 + fi +done + +# component_manifests being correct is not enough — a deploy function applying +# base manifests directly would still pass above. Stub side effects and assert +# each deploy function reaches component_manifests. +deploy_fn_for() { + case "$1" in + atenet) echo deploy_atenet ;; + ate-api-server) echo deploy_ate_apiserver ;; + atelet) echo deploy_atelet ;; + esac +} +for c in atenet ate-api-server atelet; do + fn="$(deploy_fn_for "${c}")" + ( + # shellcheck disable=SC2030,SC2031 # subshell-local env is the point: lib-mode source must not leak + export ATE_INSTALL_LIB_ONLY=1 NO_DEV_ENV=true KUBECTL_CONTEXT=verify-only + # shellcheck source=/dev/null + source hack/install-ate.sh + # shellcheck disable=SC2317 # stubs are called indirectly through "${fn}" + { + log_step() { :; } + ensure_crds() { :; } + ensure_apiserver_prerequisites() { :; } + run_kubectl() { :; } + run_ko() { :; } + component_manifests() { echo "$1" >> "${OUT}/deploy-calls"; } + } + "${fn}" + ) + if ! grep -qx "${c}" "${OUT}/deploy-calls" 2>/dev/null; then + echo "FAIL: ${fn} does not route its manifests through component_manifests ${c}" >&2 + exit 1 + fi +done + +python3 - "${OUT}" <<'EOF' +import re +import sys + +out = sys.argv[1] +ENDPOINT = "http://opentelemetry-collector.otel-system.svc:4317" +INTERVAL = ("OTEL_METRIC_EXPORT_INTERVAL", "10000") +TIMEOUT = ("OTEL_METRIC_EXPORT_TIMEOUT", "10000") + +def docs(path): + """Split a kustomize render into per-resource docs keyed by (kind, name).""" + result = {} + for doc in open(path).read().split("\n---\n"): + kind = name = None + in_metadata = False + for line in doc.splitlines(): + if line.startswith("kind: "): + kind = line.split(": ", 1)[1] + elif line == "metadata:": + in_metadata = True + elif in_metadata and line.startswith(" name: "): + name = line.split(": ", 1)[1] + in_metadata = False + elif in_metadata and not line.startswith(" "): + in_metadata = False + if kind and name: + result[(kind, name)] = doc.strip() + return result + +def container_block(doc, container): + """Return only the named container's lines: a doc-wide search would still find + the env on a ghost sibling left by a mistargeted patch. kustomize sorts map + keys, so "name:" sits inside the block, not necessarily on the dash line.""" + lines = doc.splitlines() + for ci, line in enumerate(lines): + m = re.match(r"^(\s*)containers:$", line) + if not m: + continue + indent = m.group(1) + name_line = indent + " name: " + container + dash_name_line = indent + "- name: " + container + items, start = [], None + i = ci + 1 + while i < len(lines): + ln = lines[i] + if ln.startswith(indent + "- "): + if start is not None: + items.append((start, i)) + start = i + elif ln.strip() and len(ln) - len(ln.lstrip()) <= len(indent): + break + i += 1 + if start is not None: + items.append((start, i)) + for s, e in items: + block = lines[s:e] + if block[0] == dash_name_line or name_line in block: + return "\n".join(block) + return None + +# (component overlay, workload kind, workload name, container, required env) +WORKLOADS = [ + ("atenet", "Deployment", "atenet-router", "atenet-router", + [("OTEL_EXPORTER_OTLP_ENDPOINT", ENDPOINT)]), + ("atenet", "Deployment", "dns", None, []), + ("ate-api-server", "Deployment", "ate-api-server", "ate-api-server", + [("OTEL_EXPORTER_OTLP_ENDPOINT", ENDPOINT), INTERVAL, TIMEOUT]), + ("atelet", "DaemonSet", "atelet", "atelet", + [("OTEL_EXPORTER_OTLP_ENDPOINT", ENDPOINT), INTERVAL, TIMEOUT]), +] + +root = docs(f"{out}/root.yaml") +failures = [] +for component, kind, name, container, required_env in WORKLOADS: + comp = docs(f"{out}/{component}.yaml") + key = (kind, name) + if key not in comp: + failures.append(f"{component}: {kind} {name} missing from component overlay render") + continue + if key not in root: + failures.append(f"root overlay: {kind} {name} missing") + continue + if comp[key] != root[key]: + failures.append( + f"{component}: {kind} {name} renders differently in the component overlay " + f"and the full kind overlay — the same patch must exist in exactly one place" + ) + if container is None: + continue + block = container_block(comp[key], container) + if block is None: + failures.append(f"{component}: {kind} {name} has no container {container!r}") + continue + for env_name, env_value in required_env: + # Name and value must match on the same env item: interval and timeout + # share "10000", so independent searches cross-match. kustomize quotes + # numeric-looking values. + item = re.compile( + r"- name: " + re.escape(env_name) + r"\n\s*value: \"?" + re.escape(env_value) + r"\"?(\n|$)" + ) + if not item.search(block): + failures.append( + f"{component}: {kind} {name} container {container!r} lacks " + f"{env_name}={env_value} — the env patch no longer matches it" + ) + +if failures: + print("kind overlay verification FAILED:", file=sys.stderr) + for f in failures: + print(f" - {f}", file=sys.stderr) + sys.exit(1) +print(f"kind overlays OK: {len(WORKLOADS)} workloads consistent, install routing and OTel env verified") +EOF diff --git a/manifests/ate-install/kind/ate-api-server/kustomization.yaml b/manifests/ate-install/kind/ate-api-server/kustomization.yaml new file mode 100644 index 0000000000..80b1942648 --- /dev/null +++ b/manifests/ate-install/kind/ate-api-server/kustomization.yaml @@ -0,0 +1,40 @@ +# Copyright 2026 Google LLC +# +# 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. + + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../ate-api-server.yaml + +patches: + - patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ate-api-server + namespace: ate-system + spec: + template: + spec: + containers: + - name: ate-api-server + env: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://opentelemetry-collector.otel-system.svc:4317 + - name: OTEL_METRIC_EXPORT_INTERVAL + value: "10000" + - name: OTEL_METRIC_EXPORT_TIMEOUT + value: "10000" diff --git a/manifests/ate-install/kind/atelet/kustomization.yaml b/manifests/ate-install/kind/atelet/kustomization.yaml index c70724faca..0f87df8b15 100644 --- a/manifests/ate-install/kind/atelet/kustomization.yaml +++ b/manifests/ate-install/kind/atelet/kustomization.yaml @@ -41,6 +41,10 @@ patches: env: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://opentelemetry-collector.otel-system.svc:4317 + - name: OTEL_METRIC_EXPORT_INTERVAL + value: "10000" + - name: OTEL_METRIC_EXPORT_TIMEOUT + value: "10000" - name: ATE_STORAGE_BACKEND value: s3 - name: AWS_REGION diff --git a/manifests/ate-install/kind/atenet/kustomization.yaml b/manifests/ate-install/kind/atenet/kustomization.yaml new file mode 100644 index 0000000000..4bcb7e85e3 --- /dev/null +++ b/manifests/ate-install/kind/atenet/kustomization.yaml @@ -0,0 +1,37 @@ +# Copyright 2026 Google LLC +# +# 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. + + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../atenet-router.yaml + - ../../atenet-dns.yaml + +patches: + - patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: atenet-router + namespace: ate-system + spec: + template: + spec: + containers: + - name: atenet-router + env: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://opentelemetry-collector.otel-system.svc:4317 diff --git a/manifests/ate-install/kind/kustomization.yaml b/manifests/ate-install/kind/kustomization.yaml index adc7858f4e..9981e02af2 100644 --- a/manifests/ate-install/kind/kustomization.yaml +++ b/manifests/ate-install/kind/kustomization.yaml @@ -16,17 +16,20 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - ../ate-api-server.yaml + - ./ate-api-server - ../ate-controller.yaml - ./atelet - - ../atenet-dns.yaml - - ../atenet-router.yaml + - ./atenet - ../valkey.yaml - ../pod-certificate-controller.yaml - rustfs.yaml - ./otel-collector.yaml - ./prometheus.yaml +# Per-component kind settings live in the ./ overlays above — the same +# renders the --deploy- flags apply, so a partial deploy cannot drift +# from this full render. +# # OTEL_METRIC_EXPORT_INTERVAL shortens the OTel SDK's 60s metric export tick to # 10s. A component is invisible to the collector until its first tick, and the # metrics e2e suite asserts against the collector's scrape on a bounded deadline, @@ -37,46 +40,11 @@ resources: # blip or slow DNS drops a component back to roughly one attempt per 30s, undoing # the shorter interval. Matching it to the interval keeps every tick an attempt. # -# On ate-controller both do double duty: the controller also propagates them to -# the ateom worker pods it creates, which have no other way to receive them. +# ate-controller has no per-component deploy flag, so its patch stays here. On +# it both variables do double duty: the controller also propagates them to the +# ateom worker pods it creates, which have no other way to receive them. # Kind only; production keeps the SDK defaults. patches: - - patch: |- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: ate-api-server - namespace: ate-system - spec: - template: - spec: - containers: - - name: ate-api-server - env: - - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: http://opentelemetry-collector.otel-system.svc:4317 - - name: OTEL_METRIC_EXPORT_INTERVAL - value: "10000" - - name: OTEL_METRIC_EXPORT_TIMEOUT - value: "10000" - - patch: |- - apiVersion: apps/v1 - kind: DaemonSet - metadata: - name: atelet - namespace: ate-system - spec: - template: - spec: - containers: - - name: atelet - env: - - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: http://opentelemetry-collector.otel-system.svc:4317 - - name: OTEL_METRIC_EXPORT_INTERVAL - value: "10000" - - name: OTEL_METRIC_EXPORT_TIMEOUT - value: "10000" - patch: |- apiVersion: apps/v1 kind: Deployment @@ -95,17 +63,3 @@ patches: value: "10000" - name: OTEL_METRIC_EXPORT_TIMEOUT value: "10000" - - patch: |- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: atenet-router - namespace: ate-system - spec: - template: - spec: - containers: - - name: atenet-router - env: - - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: http://opentelemetry-collector.otel-system.svc:4317