Recursive AI

Read Kubernetes manifests and multi-document YAML

All oneyaml C# recipes

A Kubernetes manifest often holds several documents separated by ---. Read each one into a record with DeserializeAll, or edit one of them in a YamlDocument and write the whole file back with its comments.

The file:

YAML
# Web frontend
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  template:
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.4.2 # pinned by release tooling
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  ports:
    - port: 80
      targetPort: 8080

The program:

C#
using OneYaml;
 
string src = File.ReadAllText("app.yaml");
 
// Typed: every document of the stream, in order.
using (var reader = new StringReader(src))
{
    foreach (var obj in YamlSerializer.DeserializeAll<K8sObject>(reader))
    {
        Console.WriteLine($"{obj!.Kind} {obj.Metadata.Name} ({obj.ApiVersion})");
    }
}
 
// Lossless: edit the Deployment, keep everything else byte for byte.
using var doc = YamlDocument.Parse(src);
int deploy = Enumerable.Range(0, doc.Documents.Count).First(i => doc.Get<string>("/kind", i) == "Deployment");
doc.Set("/spec/template/spec/containers/0/image", "registry.example.com/web:1.5.0", deploy);
Console.Write(doc);
 
record K8sObject(string ApiVersion, string Kind, Metadata Metadata);
record Metadata(string Name);
Text
Deployment web (apps/v1)
Service web (v1)
# Web frontend
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  template:
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.5.0 # pinned by release tooling
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  ports:
    - port: 80
      targetPort: 8080

Good to know

  • YamlSerializer.DeserializeAll<T> reads every document from a TextReader or a Stream; DeserializeAllAsync streams them as an IAsyncEnumerable<T>. SerializeAll writes a list back as a stream.
  • A YamlDocument holds the whole stream. Documents lists the root of each, Root is the first, and every pointer method and patch takes a document index, 0 by default. Output always writes the whole stream.
  • For manifests from outside, set MaxDocuments and the other limits (read untrusted YAML safely).