A Kubernetes manifest often holds several documents separated by ---. Read each one into a struct with a Decoder, or edit one of them in a Document and write the whole file back with its comments.
The file:
# 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:
package main import ( "bytes" "errors" "fmt" "io" "log" "os" oneyaml "oneyaml.dev/go" ) type Object struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` Metadata struct { Name string `yaml:"name"` } `yaml:"metadata"` } func main() { src, err := os.ReadFile("app.yaml") if err != nil { log.Fatal(err) } // Typed: a Decoder reads one document per call, until io.EOF. dec := oneyaml.NewDecoder(bytes.NewReader(src)) for { var obj Object err := dec.Decode(&obj) if errors.Is(err, io.EOF) { break } if err != nil { log.Fatal(err) } fmt.Printf("%s %s (%s)\n", obj.Kind, obj.Metadata.Name, obj.APIVersion) } // Lossless: edit the Deployment, keep everything else byte for byte. stream, err := oneyaml.Parse(src) if err != nil { log.Fatal(err) } for i := 0; i < stream.Len(); i++ { doc := stream.Doc(i) var kind string if doc.Get("/kind", &kind) == nil && kind == "Deployment" { if err := doc.Set("/spec/template/spec/containers/0/image", "registry.example.com/web:1.5.0"); err != nil { log.Fatal(err) } } } fmt.Print(stream) }
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
Decoder.Decodereads one document per call and returnsio.EOFafter the last.Unmarshalreads only the first document.- A parsed
Documentholds the whole stream.Len()counts its documents, andDoc(i)is a view of one that reads and edits only that document. - For manifests from outside, use
Untrusted(): it also bounds input size, documents per stream and scalar length (read untrusted YAML safely). - Kubernetes code built on
sigs.k8s.io/yamlcan switch tooneyaml.dev/go/compat/k8syamlwith one import change (migration guide).