From 876b4237a031ac1bd0f50a93ccdfdc91f7019132 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Tue, 22 Sep 2020 15:19:46 -0400 Subject: [PATCH] hclsyntax: Pass marks through template expressions If a template expression interpolates values which have marks, we should apply all of those marks to the output value. This allows template expressions to function like native cty functions with respect to marks. --- hclsyntax/expression_template.go | 19 ++++++++++++++++--- hclsyntax/expression_template_test.go | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/hclsyntax/expression_template.go b/hclsyntax/expression_template.go index 9d425115..ff9a6e5c 100644 --- a/hclsyntax/expression_template.go +++ b/hclsyntax/expression_template.go @@ -26,6 +26,9 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) var diags hcl.Diagnostics isKnown := true + // Maintain a set of marks for values used in the template + marks := make(cty.ValueMarks) + for _, part := range e.Parts { partVal, partDiags := part.Value(ctx) diags = append(diags, partDiags...) @@ -71,14 +74,24 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) continue } - buf.WriteString(strVal.AsString()) + // Unmark the part and merge its marks into the set + unmarked, partMarks := strVal.Unmark() + for k, v := range partMarks { + marks[k] = v + } + + buf.WriteString(unmarked.AsString()) } + var ret cty.Value if !isKnown { - return cty.UnknownVal(cty.String), diags + ret = cty.UnknownVal(cty.String) + } else { + ret = cty.StringVal(buf.String()) } - return cty.StringVal(buf.String()), diags + // Apply the full set of marks to the returned value + return ret.WithMarks(marks), diags } func (e *TemplateExpr) Range() hcl.Range { diff --git a/hclsyntax/expression_template_test.go b/hclsyntax/expression_template_test.go index 81dda0ba..90e0eb6b 100644 --- a/hclsyntax/expression_template_test.go +++ b/hclsyntax/expression_template_test.go @@ -283,6 +283,27 @@ trim`, cty.UnknownVal(cty.String), 1, // Unexpected endfor directive }, + { // marks from uninterpolated values are ignored + `hello%{ if false } ${target}%{ endif }`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "target": cty.StringVal("world").WithMarks(cty.NewValueMarks("sensitive")), + }, + }, + cty.StringVal("hello"), + 0, + }, + { // marks from interpolated values are passed through + `${greeting} ${target}`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "greeting": cty.StringVal("hello").WithMarks(cty.NewValueMarks("english")), + "target": cty.StringVal("world").WithMarks(cty.NewValueMarks("sensitive")), + }, + }, + cty.StringVal("hello world").WithMarks(cty.NewValueMarks("english", "sensitive")), + 0, + }, } for _, test := range tests {