1

I parse the struct from values.yaml and want to use it in template.yaml

Here is my values.yaml file:

services:
  app:
    image: matryoshka/app
    replicaCount: 1
  cron:
    image: matryoshka/cron
    replicaCount: 1

And here is my template.yaml (NOT VALID CODE):

{{- range $key, $value := .Services}}
    {{$key}}{{$value}}
{{- end}}

Which gives me error:

panic: template: template.yaml:1:26: executing "template.yaml" at <.Services>: range can't iterate over {{atryoshka/app 1} {matryoshka/cron 1}}

Here is my .go code:

package main

import (
    "html/template"
    "io/ioutil"
    "os"
    "path/filepath"

    "gopkg.in/yaml.v2"
)

type Values struct {
    Services struct {
        App struct {
            Image        string `yaml:"image"`
            ReplicaCount string `yaml:"replicaCount"`
        } `yaml:"app"`
        Cron struct {
            Image        string `yaml:"image"`
            ReplicaCount string `yaml:"replicaCount"`
        } `yaml:"cron"`
    }
}

func parseValues() Values {

    var values Values
    filename, _ := filepath.Abs("./values.yaml")
    yamlFile, err := ioutil.ReadFile(filename)

    err = yaml.Unmarshal(yamlFile, &values)
    if err != nil {
        panic(err)
    }

    return values

}
func insertValues(class Values) {
    paths := []string{"template.yaml"}
    t, err := template.New(paths[0]).ParseFiles(paths...)
    if err != nil {
        panic(err)
    }
    err = t.Execute(os.Stdout, class)
    if err != nil {
        panic(err)
    }
}

func main() {
    values := parseValues()
    insertValues(values)
}

How can I iterate over .Services in template.yaml correctly? I found only option with {{- range $key, $value := .Services}} but it doesn't work.

icza
  • 389,944
  • 63
  • 907
  • 827
Snobby
  • 1,067
  • 3
  • 18
  • 38

1 Answers1

3

You can't range over fields of a struct, as you experienced. You can only range over slices, arrays, maps and channels.

Using a map

So easiest would be to pass that: a map. You can directly unmarshal a YAML into a map or the empty interface:

func parseValues() interface{} {
    var values interface{}
    // ...rest is unchanged
}

func insertValues(class interface{}) {
    // ...unchanged
}

Changing a little the format of your template (note the .services):

{{- range $key, $value := .services}}
{{$key}} {{$value}}
{{- end}}

With these, it works and output is:

app map[replicaCount:1 image:matryoshka/app]
cron map[image:matryoshka/cron replicaCount:1]

Using a slice

If you want to keep using your Services model, another option would be to prepare and pass a slice of the fields manually:

insertValues([]interface{}{values.Services.App, values.Services.Cron})

And then the template:

{{- range $key, $value := .}}
{{$key}} {{$value}}
{{- end}}

And then the output:

0 {matryoshka/app 1}
1 {matryoshka/cron 1}

Using a slice and reflection

If you want it to remain "dynamic" (meaning you don't have to enumerate fields manually), you can create a helper function which does that using reflection. For an example how to do that, see Get all fields from an interface and Iterate through the fields of a struct in Go.

icza
  • 389,944
  • 63
  • 907
  • 827