From 9dea4a7f67ecce3dbf11c1ffdab8239ea0f574bb Mon Sep 17 00:00:00 2001 From: "SREDevOps.org Agent" Date: Wed, 21 Oct 2026 14:11:40 -0300 Subject: [PATCH] Normalise the pod model returned by CoreV1Api (#7) get_pod() returned kubernetes' V1Pod model, but dsh_cookie() indexes it as a dict (pod.get("metadata")), so every gateway handshake crashed with: AttributeError: 'V1Pod' object has no attribute 'get' and every proxied request answered 500. The unit tests inject a fake that returns dicts, so the mismatch only appeared against a real API server. Normalise at the adapter boundary with ApiClient().sanitize_for_serialization and cover it with a test that returns a real V1Pod. Also document digest-pinned gateway rollouts: the workflow publishes the mutable :gateway tag, and imagePullPolicy: IfNotPresent keeps the cached old image across kubectl apply / rollout restart. Co-authored-by: SREDevOpsAgents --- examples/dsh-agents-as-a-service/README.md | 12 ++++++++++++ .../gateway/gateway.py | 6 +++++- .../gateway/test_gateway.py | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/examples/dsh-agents-as-a-service/README.md b/examples/dsh-agents-as-a-service/README.md index ba0189c..cebff00 100644 --- a/examples/dsh-agents-as-a-service/README.md +++ b/examples/dsh-agents-as-a-service/README.md @@ -198,6 +198,18 @@ kubectl -n dsh-demo port-forward svc/dsh-gateway 18080:8080 > The gateway binds `8080`; the local port-forward uses `18080` and never `3080`. > +> **Re-deploying after a new image?** The workflow publishes the mutable `:gateway` tag and the +> Deployment uses `imagePullPolicy: IfNotPresent`, so a node that already cached the tag keeps +> the old image across `kubectl apply`/`rollout restart`. Pin the digest for a reproducible +> rollout (the workflow's `Inspect image` step and `docker buildx imagetools inspect` both show +> it): +> +> ```bash +> kubectl -n dsh-demo set image deploy/dsh-gateway \ +> gateway=ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox@sha256: +> kubectl -n dsh-demo rollout status deploy/dsh-gateway --timeout=120s +> ``` +> > **Applying over an existing install?** Run `kubectl diff -f 10-sandboxtemplate.yaml` > first. Cluster-specific fields such as `nodeAffinity` and the warm-pool replica count are > intentionally not in the repo manifests; preserve them with a targeted patch instead of a diff --git a/examples/dsh-agents-as-a-service/gateway/gateway.py b/examples/dsh-agents-as-a-service/gateway/gateway.py index 81ffd45..ccd0cc8 100644 --- a/examples/dsh-agents-as-a-service/gateway/gateway.py +++ b/examples/dsh-agents-as-a-service/gateway/gateway.py @@ -475,13 +475,17 @@ class Kubernetes: async def get_pod(self, name: str) -> dict | None: try: - return await self._call( + pod = await self._call( "core", "read_namespaced_pod", name, self.namespace ) except ApiException as exc: if exc.status == 404: return None raise + # CoreV1Api returns a V1Pod model while every other call in this module + # (CRD calls, fakes in tests) yields a dict. Normalise at the boundary + # so callers can keep using ``pod.get(...)``. + return k8s_client.ApiClient().sanitize_for_serialization(pod) async def read_pod_logs( self, name: str, container: str, tail_lines: int diff --git a/examples/dsh-agents-as-a-service/gateway/test_gateway.py b/examples/dsh-agents-as-a-service/gateway/test_gateway.py index 276c588..e8c6f89 100644 --- a/examples/dsh-agents-as-a-service/gateway/test_gateway.py +++ b/examples/dsh-agents-as-a-service/gateway/test_gateway.py @@ -497,6 +497,25 @@ async def test_kubernetes_read_pod_logs_forwards_container_and_tail(monkeypatch) assert calls == [(("pod-1", "test-ns"), {"container": "dsh-web", "tail_lines": 200})] +@pytest.mark.asyncio +async def test_kubernetes_get_pod_normalises_the_v1pod_model(monkeypatch): + """CoreV1Api returns a V1Pod model; callers index it like a dict.""" + from kubernetes.client import V1ObjectMeta, V1Pod + + from gateway import Kubernetes + + async def fake_call(which, method, *args): + return V1Pod(metadata=V1ObjectMeta(name="dsh-alice", uid="pod-uid-9")) + + k8s = Kubernetes("test-ns") + monkeypatch.setattr(k8s, "_call", fake_call) + + pod = await k8s.get_pod("dsh-alice") + + assert isinstance(pod, dict) + assert pod["metadata"]["uid"] == "pod-uid-9" + + # --- health / landing / redirect ------------------------------------------