Skip to content

Instantly share code, notes, and snippets.

@sparkprime
Last active March 27, 2016 00:11
Show Gist options
  • Save sparkprime/3f9887a77523a8b4094e to your computer and use it in GitHub Desktop.
Save sparkprime/3f9887a77523a8b4094e to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/Masterminds/sprig"
"github.com/cloudfoundry-incubator/candiedyaml"
"github.com/ghodss/yaml"
"io"
"strings"
"text/template"
)
// parseYAMLStream takes an encoded YAML stream and turns it into a slice of JSON-marshalable
// objects, one for each document in the stream.
func parseYAMLStream(in io.Reader) ([]interface{}, error) {
// Use candiedyaml because it's the only one that supports streams.
decoder := candiedyaml.NewDecoder(in)
var document interface{}
stream := []interface{}{}
for {
err := decoder.Decode(&document)
if err != nil {
if strings.Contains(err.Error(), "Expected document start at line") {
return stream, nil
}
return nil, err
}
// Now it's held in document but we have to do a bit of a dance to get it in a form that can
// be marshaled as JSON. Luckily, ghodss/yaml has code to help with this:
// 1) Marshal it // back to YAML string.
yamlBytes, err := candiedyaml.Marshal(document)
if err != nil {
return nil, err
}
var jsonObj interface{}
// 2) Use ghodss/yaml to unmarshal it into JSON-compatible data structures.
err = yaml.Unmarshal(yamlBytes, &jsonObj)
if err != nil {
return nil, err
}
stream = append(stream, jsonObj)
}
}
type file struct {
path string
content []byte
}
// TODO(dcunnin): Remove non-hermetic functions
// TODO(dcunnin): JSONSchema
// executeAllTemplates resolves the given files to a sequence of JSON-marshalable values.
func executeAllTemplates(files []file, params interface{}) ([]interface{}, error) {
resources := []interface{}{}
for _, file := range files {
name := file.path
content := file.content
tmpl := template.New(name).Funcs(sprig.TxtFuncMap())
for _, otherFile := range files {
otherName := otherFile.path
otherContent := otherFile.content
if name == otherName {
continue
}
_, err := tmpl.Parse(string(otherContent))
if err != nil {
return nil, err
}
}
// Have to put something in that resolves non-empty or Go templates get confused.
_, err := tmpl.Parse("# Content begins now")
if err != nil {
return nil, err
}
tmpl, err = tmpl.Parse(string(content))
if err != nil {
return nil, err
}
generated := bytes.NewBuffer(nil)
err = tmpl.ExecuteTemplate(generated, name, params)
if err != nil {
return nil, err
}
stream, err := parseYAMLStream(generated)
if err != nil {
return nil, fmt.Errorf("%s\nContent:\n%s", err.Error(), generated)
}
for _, doc := range stream {
resources = append(resources, doc)
}
}
return resources, nil
}
func main() {
files := []file{
{
"templates/bar.tmpl",
[]byte(`
{{ template "banana" . }}
`),
},
{
"templates/base.tmpl",
[]byte(`
{{ define "apple" }}
name: Abby
kind: Apple
dbname: {{default "whatdb" .DatabaseName}}
{{ end }}
{{ define "banana" }}
name: Bobby
kind: Banana
dbname: {{default "whatdb" .DatabaseName}}
{{ end }}
`),
},
{
"templates/foo.tmpl",
[]byte(`
---
foo:
bar: baz
---
{{ template "apple" . }}
---
{{ template "apple" . }}
...
`),
},
{
"templates/docs.txt",
[]byte(`
{{/*
File contains only a comment. Suitable for documentation within templates/
*/}}
`),
},
}
type ChartParams struct {
DatabaseName string
NumReplicas int
}
params := ChartParams{"mydb", 3}
resources, err := executeAllTemplates(files, params)
if err != nil {
panic(err)
}
for i, resource := range resources {
json_bytes, err := json.MarshalIndent(resource, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("resource[%d]: %s\n", i, string(json_bytes))
}
}
@sparkprime
Copy link
Author

output:

resource[0]: {
    "dbname": "mydb",
    "kind": "Banana",
    "name": "Bobby"
}
resource[1]: {
    "foo": {
        "bar": "baz"
    }
}
resource[2]: {
    "dbname": "mydb",
    "kind": "Apple",
    "name": "Abby"
}
resource[3]: {
    "dbname": "mydb",
    "kind": "Apple",
    "name": "Abby"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment