How to encode comments while generating a config file in Go and API v3 of the yaml package

Viewed 848

I'm trying to learn to use YAML v3 while marshaling a struct to config yaml.

Note: Update: My original example was little over simplified. My actual requirements is below:

For example, I have:

package main

import (
    "fmt"
    "io/ioutil"

    "gopkg.in/yaml.v3"
)

type Ec2 struct {
    Class string `yaml:"class"`
    Ip    string `yaml:"ip"`
}

type Vpc struct {
    Subnet string `yaml:"subnet"`
    Igw    interface{} `yaml:"igw"`

}

type Config struct {
    Ec2 Ec2 `yaml:"ec2"`
    Vpc Vpc `yaml:"vpc"`
}

func main() {
    c := Config{}
    bytes, err := yaml.Marshal(c)
    if err != nil {
        fmt.Println(err)
    }

    ioutil.WriteFile("config.yaml", bytes, 0644)
}

Which create a file named config.yaml file in pwd like:

  ec2:
    class: ""
    ip: ""
  vpc:
    subnet: ""
    igw: null

But I am trying to generate config file (in this case config.yaml) like:


    ec2:
    # This section is for EC2
      class: ""
      ip: ""
    vpc:
    # This section is for VPC
      subnet: ""
      igw: null

Can this be achievable using V.3?

1 Answers

There are two methods to leverage yaml.v3 and its comments feature:

  • build a yaml.Node tree by hand (tedious maintenance; precise value updates)
  • parse a template into a yaml.Node tree (easy maintenance; harder to value update)

Method 1:

Craft a YAML Node tree by hand:

config = &yaml.Node{
    Kind: yaml.DocumentNode,
    Content: []*yaml.Node{
        &yaml.Node{
            Kind: yaml.MappingNode,
            Content: []*yaml.Node{
                &yaml.Node{
                    Kind:        yaml.ScalarNode,
                    Value:       "ec2",
                    HeadComment: "# This section is for EC2",
                },
                &yaml.Node{
                    Kind:  yaml.ScalarNode,
                    Style: yaml.DoubleQuotedStyle,
                    Value: "", // <- field value goes here
                },

                &yaml.Node{
                    Kind:        yaml.ScalarNode,
                    Value:       "vpc",
                    HeadComment: "# This section is for VPC",
                },
                &yaml.Node{
                    Kind:  yaml.ScalarNode,
                    Style: yaml.DoubleQuotedStyle,
                    Value: "", // <- field value goes here
                },
            },
        },
    },
}

Working Playground example: https://play.golang.org/p/Z6iw29rHZXk

Output:

# This section is for EC2
ec2: ""
# This section is for VPC
vpc: ""

Method 2:

Read a template of your YAML schema e.g.

var yamlSchema = []byte(`

# EC2 header-comment
ec2: ""

# This section is for VPC
vpc: ""

`)

And then parse it at runtime into a yaml.Node tree like so:

var c yaml.Node
err := yaml.Unmarshal(yamlSchema, &c)

But as a yaml.Node tree - which has nested slice of yaml.Nodes - you'd need to programmatically find your field values so when you Marshal the config for writing, the values are paired with the correct fields.

For example, this may work today:

c.Content[0].Content[1].Value = ec2Value // dangerous

but not tomorrow, when the schema changes.

Traversing the map and looking for the field value ec2 and then placing its value in the adjacent node would be the proper way.

Playground example shows how header/footer/inline comments can be added to the schema e.g.

# This section is for EC2
ec2: ""          # <-- place user input here
# footer EC2

# This section is for VPC
vpc: ""
Related