Recursive AI

Read Kubernetes manifests and multi-document YAML

All oneyaml Rust recipes

A Kubernetes manifest often holds several documents separated by ---. Read each one into a struct, or edit one of them 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:

RUST
use oneyaml::{Deserializer, Stream};
use serde::Deserialize;
 
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Object {
    api_version: String,
    kind: String,
    metadata: Metadata,
}
 
#[derive(Debug, Deserialize)]
struct Metadata {
    name: String,
}
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = std::fs::read_to_string("app.yaml")?;
 
    // Typed: one Deserializer per document.
    for doc in Deserializer::from_str(&src) {
        let obj = Object::deserialize(doc)?;
        println!("{} {} ({})", obj.kind, obj.metadata.name, obj.api_version);
    }
 
    // Lossless: edit the Deployment, keep everything else byte for byte.
    let mut stream = Stream::parse(src)?;
    let i = stream
        .documents()
        .position(|d| d.get_path("kind").and_then(|n| n.as_str()).as_deref() == Some("Deployment"))
        .ok_or("no Deployment")?;
    let mut deploy = stream.document_mut(i).ok_or("no document")?;
    deploy.set_path("spec.template.spec.containers[0].image", "registry.example.com/web:1.5.0")?;
    print!("{stream}");
    Ok(())
}
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

  • Iterating a Deserializer yields one deserializer per document, in order. A document you don’t deserialize before asking for the next is skipped.
  • oneyaml::from_str reads a single document. Given a stream with more than one, it’s an error, not a silent read of the first.
  • Stream::parse keeps every document with its comments. stream.documents() gives a read view of each, and document_mut(i) edits one in place. Each document writes its own region of the file, so the Service’s text is untouched.
  • The default limits include a cap on documents per stream. For manifests from outside, use Limits::untrusted() (read untrusted YAML safely).

The crate repository has a fuller example that edits a Deployment and a Service and checks every comment’s owner: k8s_manifest.rs.