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.
You can't range over fields of a struct, as you experienced. You can only range over slices, arrays, maps and channels.
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]
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}
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.