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 ------------------------------------------