Kubernetes · Tooling
The List
That Broke Five Harnesses
Every static check that reads a Kubernetes manifest assumes the same input shape: one or more YAML documents, separated by `---`. That assumption is correct for `kustomize build` and `helm template`. It's wrong for one of the most natural ways to dump live cluster state, and five separate tools had built the same blind spot into themselves without anyone noticing.
A set of Kubernetes anti-pattern checkers — five of them, each reading manifests independently to catch a different category of mistake — had all been validated the same way: pipe rendered YAML in, confirm the right verdict comes out. `kustomize build`, `helm template`, a raw manifest file. All of it arrives as one or more `---`-separated documents, and every checker's loader was written, reasonably, to split on that separator and parse each chunk.
A Different Way to Ask Kubernetes for the Same Thing
Validating a check against a genuinely running cluster means asking the cluster itself what's live, not just what was declared. The natural way to do that for more than one resource at once is kubectl get deployment app-a app-b -o yaml — name several resources, get their full manifests back in one call instead of one request per resource.
kubectl does return full manifests. It just doesn't return them the way `kustomize` or `helm` would. Naming two or more resources in one get call wraps the result in a single document: kind: List, with every requested resource nested under an items: array. No --- separator anywhere, because there's only one top-level document to begin with.
A loader written to split on --- and parse each chunk as one resource, handed a kind: List document instead, parses it as exactly one resource — a resource of kind List, which no check was written to recognize. Every rule that pattern-matches on kind: Deployment, kind: NetworkPolicy, and so on simply finds nothing to match. Not an error. Not a crash. A clean, silent "no resources found."
Five for Five, Not One
This wasn't one checker's parsing bug. Every checker in the set shared the same loader convention — split on ---, parse each chunk — because it had always been sufficient before. The moment a live-cluster validation round started feeding real kubectl get output with multiple resources per call, all five inherited the identical blind spot at once, for the identical reason. A single fix — detect kind: List and unwrap its items into the same document stream the rest of the loader already expected — closed it everywhere at once, which was itself a small confirmation that the five checkers had been sharing more implementation than their separate anti-pattern responsibilities suggested.
Why "No Resources Found" Is the Dangerous Failure Mode
A checker that crashes on unexpected input is annoying but honest — it tells you immediately that something needs fixing. A checker that silently finds zero resources to check looks, from the outside, identical to a checker confirming a clean pass. Nothing in the checker's own output distinguishes "I looked and found no violations" from "I looked at nothing." Anyone piping real kubectl get output with more than one resource per call through any of these checks would have gotten a green result — not because the resources were compliant, but because the checker never actually saw them.
Any tool that parses Kubernetes YAML by assuming a particular document boundary is only as correct as the set of tools it was tested against producing that boundary. kustomize build, helm template, and single-resource kubectl get -o yaml all agree on ----separated documents. Naming more than one resource in a single kubectl get call doesn't — and that specific shape is easy to never trigger in testing if every fixture was built from rendered files rather than a live cluster.
What Changed
The fix isn't clever: check whether the top-level parsed document has kind: List, and if so, treat its items array as the document stream instead of the document itself. Cheap, a few lines, and it means a loader now accepts every shape the tools it actually gets fed can produce — not just the shape that happened to be the one used to build the test fixtures.
kyhsa93/k8s-playbook — where this surfaced, validating a Kubernetes anti-pattern harness against a real Argo CD-managed cluster instead of just rendered fixtures