Skip to content
Snippets Groups Projects
Commit ba018e474798 authored by steeve.chailloux's avatar steeve.chailloux
Browse files

improved hydrate function

can now hydrate multiline strings and/or raw yaml node
parent 977f0c7d0699
No related branches found
No related tags found
No related merge requests found
# Beaver
[#](#) Beaver
```
____
......
package runner
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
......@@ -16,7 +17,7 @@
// Variable ...
type Variable struct {
Name string
Value string
Value interface{}
}
type Sha struct {
......@@ -422,8 +423,133 @@
return files
}
func hydrateString(input string, output io.Writer, variables map[string]interface{}) error {
t, err := fasttemplate.NewTemplate(input, "<[", "]>")
if err != nil {
return fmt.Errorf("unexpected error when parsing template: %w", err)
}
s, err := t.ExecuteFuncStringWithErr(func(w io.Writer, tag string) (int, error) {
val, ok := variables[tag]
if !ok {
return 0, fmt.Errorf("tag not found: %s", tag)
}
switch v := val.(type) {
case string:
return w.Write([]byte(v))
default:
e := yaml.NewEncoder(w)
err := e.Encode(val)
return 0, err
}
})
if err != nil {
return err
}
if _, err := output.Write([]byte(s)); err != nil {
return fmt.Errorf("failed to template: %w", err)
}
return nil
}
func hydrateScalarNode(node *yaml.Node, variables map[string]interface{}) error {
input := node.Value
var output interface{}
if strings.HasPrefix(input, "<[") && strings.HasSuffix(input, "]>") {
tag := strings.Trim(input, "<[]>")
var ok bool
output, ok = variables[tag]
if !ok {
return fmt.Errorf("tag not found: %s", tag)
}
} else {
buf := bytes.NewBufferString("")
if err := hydrateString(input, buf, variables); err != nil {
return err
}
output = buf.String()
}
// preserve comments
hc := node.HeadComment
lc := node.LineComment
fc := node.FootComment
if err := node.Encode(output); err != nil {
return err
}
node.HeadComment = hc
node.LineComment = lc
node.FootComment = fc
return nil
}
func hydrateYamlNodes(nodes []*yaml.Node, variables map[string]interface{}) error {
for _, node := range nodes {
if node.Kind == yaml.ScalarNode {
if err := hydrateScalarNode(node, variables); err != nil {
return fmt.Errorf("failed to parse scalar: %w", err)
}
} else {
if err := hydrateYamlNodes(node.Content, variables); err != nil {
return fmt.Errorf("failed to hydrate content: %w", err)
}
}
}
return nil
}
func hydrateYaml(root *yaml.Node, variables map[string]interface{}) error {
err := hydrateYamlNodes(root.Content, variables)
return err
}
func Hydrate(input []byte, output io.Writer, variables map[string]interface{}) error {
documents := bytes.Split(input, []byte("---\n"))
// yaml lib ignore leading '---'
// see: https://github.com/go-yaml/yaml/issues/749
// which is an issue for ytt value files
// this is why we loop over documents in the same file
for i, doc := range documents {
var node yaml.Node
if err := yaml.Unmarshal(doc, &node); err != nil || len(node.Content) == 0 {
// not a yaml template, fallback to raw template method
// ...maybe a ytt header or a frontmatter
template := string(doc)
if err := hydrateString(template, output, variables); err != nil {
return err
}
} else {
// FIXME: do not call this method when hydrating only for sha,
// could be quite expensive
// yaml template method
err := hydrateYaml(&node, variables)
if err != nil {
return fmt.Errorf("failed to hydrate yaml: %w", err)
}
o, err := yaml.Marshal(node.Content[0])
if err != nil {
return fmt.Errorf("failed to marshal yaml: %w", err)
}
_, err = output.Write(o)
if err != nil {
return err
}
}
if i != len(documents)-1 {
_, err := output.Write([]byte("---\n"))
if err != nil {
return err
}
}
}
return nil
}
func hydrate(input string, output *os.File, variables map[string]interface{}) error {
byteTemplate, err := os.ReadFile(input)
if err != nil {
return fmt.Errorf("failed to read %s: %w", input, err)
}
......@@ -425,19 +551,9 @@
func hydrate(input string, output *os.File, variables map[string]interface{}) error {
byteTemplate, err := os.ReadFile(input)
if err != nil {
return fmt.Errorf("failed to read %s: %w", input, err)
}
template := string(byteTemplate)
t, err := fasttemplate.NewTemplate(template, "<[", "]>")
if err != nil {
return fmt.Errorf("unexpected error when parsing template: %w", err)
}
s := t.ExecuteString(variables)
if _, err := output.Write([]byte(s)); err != nil {
return fmt.Errorf("failed to template for %s: %w", output.Name(), err)
}
return nil
return Hydrate(byteTemplate, output, variables)
}
func hydrateFiles(tmpDir string, variables map[string]interface{}, paths []string) ([]string, error) {
......
......@@ -30,6 +30,47 @@
assert.Equal(t, "../vendor/ytt/odoo", config.Charts["odoo"].Path)
}
func TestHydrate(t *testing.T) {
rawVariables := []byte(`
#@data/values
---
foo: |
a multi
line string
bar:
simple: interface
with:
- some
- content
baz: |
only one line in multiline mode
boo: a simple joke line
`)
variables := make(map[string]interface{})
byteContent := bytes.NewReader(rawVariables)
decoder := yaml.NewDecoder(byteContent)
require.NoError(t, decoder.Decode(&variables))
input := `
#@data/values
---
foo: <[foo]>
bar: <[bar]>
baz: <[baz]>
boo: <[boo]>
`
buf := bytes.NewBufferString("")
require.NoError(t, runner.Hydrate([]byte(input), buf, variables))
assert.Equal(
t,
string(rawVariables),
buf.String(),
)
}
func TestYttBuildArgs(t *testing.T) {
tl := testutils.NewTestLogger(t)
testNS := "environments/ns1"
......@@ -100,7 +141,7 @@
}
func TestSha(t *testing.T) {
shaValue := "2145bea9e32804c65d960e6d4af1c87f95ccc39fad7df5eec2f3925a193112ab"
shaValue := "33935340f50ff18c3837c8cf42a423f6be96df7886723a3994a6018b0cc97e01"
buildDir := filepath.Join(shaFixtures, "build", "example")
defer func() {
require.NoError(t, runner.CleanDir(buildDir))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment