Skip to content

Commit 3a9ab94

Browse files
committed
fix: escape non-scalar literals in variables
fixes #559 Signed-off-by: Max Brauer <mbrauer@vmware.com>
1 parent 94385ee commit 3a9ab94

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

internal/builder/interpolator.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/pivotal-cf/kiln/pkg/proofing"
1414

1515
yamlConverter "github.com/ghodss/yaml"
16-
"gopkg.in/yaml.v2"
16+
"gopkg.in/yaml.v3"
1717
)
1818

1919
const (
@@ -274,8 +274,8 @@ func (i Interpolator) interpolateValueIntoYAML(input InterpolateInput, name stri
274274
return "", err // should never happen
275275
}
276276

277-
if stringScalar, ok := val.(string); ok {
278-
return stringScalar, nil
277+
if stringVal, ok := val.(string); ok {
278+
return i.yamlMarshalString(stringVal)
279279
}
280280

281281
interpolatedYAML, err := i.interpolate(input, name, initialYAML)
@@ -291,6 +291,39 @@ func (i Interpolator) interpolateValueIntoYAML(input InterpolateInput, name stri
291291
return string(inlinedYAML), nil
292292
}
293293

294+
// yamlMarshalString escapes a string so it can be embedded into a YAML document literally
295+
//
296+
// For example:
297+
// - "hello" → "hello"
298+
// - "true" → "true"
299+
// - "[one, 2]" → "\"[one, 2]\""
300+
func (i Interpolator) yamlMarshalString(val string) (string, error) {
301+
var node yaml.Node
302+
err := yaml.Unmarshal([]byte(val), &node)
303+
if err != nil {
304+
return "", err
305+
}
306+
307+
// when it's a document node jump to the content node
308+
contentNode := &node
309+
if len(node.Content) > 0 {
310+
contentNode = node.Content[0]
311+
}
312+
313+
// when the node is a scalar, e.g. string, int, double, etc., no escaping is needed
314+
if contentNode.Kind == yaml.ScalarNode {
315+
return val, nil
316+
}
317+
318+
// escape objects and arrays
319+
var escaped []byte
320+
escaped, err = yaml.Marshal(val)
321+
if err != nil {
322+
return "", err
323+
}
324+
return string(escaped), nil
325+
}
326+
294327
// Workaround to avoid YAML indentation being incorrect when value is interpolated into the metadata
295328
func (i Interpolator) yamlMarshalOneLine(yamlContents []byte) ([]byte, error) {
296329
return yamlConverter.YAMLToJSON(yamlContents)

internal/builder/interpolator_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,29 @@ some_form_types:
272272
`))
273273
})
274274

275+
It("interpolates literal variables", func() {
276+
templateYAML := `
277+
---
278+
literal_object: $(variable "variable-literal-object")
279+
literal_array: $(variable "variable-literal-array")
280+
`
281+
282+
input = builder.InterpolateInput{
283+
SkipKilnMetadata: true,
284+
Variables: map[string]any{
285+
"variable-literal-object": `{"some": "value"}`,
286+
"variable-literal-array": `[some, "things"]`,
287+
},
288+
}
289+
290+
interpolatedYAML, err := interpolator.Interpolate(input, "", []byte(templateYAML))
291+
Expect(err).NotTo(HaveOccurred())
292+
Expect(interpolatedYAML).To(HelpfullyMatchYAML(`
293+
literal_object: "{\"some\": \"value\"}"
294+
literal_array: "[some, \"things\"]"
295+
`))
296+
})
297+
275298
Context("when multiple stemcells are specified", func() {
276299
var templateYAML string
277300

0 commit comments

Comments
 (0)