-
Notifications
You must be signed in to change notification settings - Fork 786
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added templatestring function similar to templatefile (#1223)
Signed-off-by: sanskruti-shahu <sanskruti.shahu@harness.io> Signed-off-by: Sanskruti Shahu <76054960+sanskruti-shahu@users.noreply.github.com> Co-authored-by: James Humphries <James@james-humphries.co.uk>
- Loading branch information
1 parent
659242f
commit 835dcb8
Showing
12 changed files
with
517 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright (c) The OpenTofu Authors | ||
// SPDX-License-Identifier: MPL-2.0 | ||
// Copyright (c) 2023 HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package funcs | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/hcl/v2/hclsyntax" | ||
"github.com/zclconf/go-cty/cty" | ||
"github.com/zclconf/go-cty/cty/function" | ||
) | ||
|
||
func renderTemplate(expr hcl.Expression, varsVal cty.Value, funcsCb func() map[string]function.Function) (cty.Value, error) { | ||
if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) { | ||
return cty.DynamicVal, function.NewArgErrorf(1, "invalid vars value: must be a map") // or an object, but we don't strongly distinguish these most of the time | ||
} | ||
|
||
ctx := &hcl.EvalContext{ | ||
Variables: varsVal.AsValueMap(), | ||
} | ||
|
||
// We require all of the variables to be valid HCL identifiers, because | ||
// otherwise there would be no way to refer to them in the template | ||
// anyway. Rejecting this here gives better feedback to the user | ||
// than a syntax error somewhere in the template itself. | ||
for n := range ctx.Variables { | ||
if !hclsyntax.ValidIdentifier(n) { | ||
// This error message intentionally doesn't describe _all_ of | ||
// the different permutations that are technically valid as an | ||
// HCL identifier, but rather focuses on what we might | ||
// consider to be an "idiomatic" variable name. | ||
return cty.DynamicVal, function.NewArgErrorf(1, "invalid template variable name %q: must start with a letter, followed by zero or more letters, digits, and underscores", n) | ||
} | ||
} | ||
|
||
// currFilename stores the filename of the template file, if any. | ||
currFilename := expr.Range().Filename | ||
|
||
// We'll pre-check references in the template here so we can give a | ||
// more specialized error message than HCL would by default, so it's | ||
// clearer that this problem is coming from a templatefile/templatestring call. | ||
for _, traversal := range expr.Variables() { | ||
root := traversal.RootName() | ||
referencedPos := fmt.Sprintf("%q", root) | ||
if currFilename != templateStringFilename { | ||
referencedPos = fmt.Sprintf("%q, referenced at %s", root, traversal[0].SourceRange()) | ||
} | ||
if _, ok := ctx.Variables[root]; !ok { | ||
return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %s", referencedPos) | ||
} | ||
} | ||
|
||
givenFuncs := funcsCb() // this callback indirection is to avoid chicken/egg problems | ||
funcs := make(map[string]function.Function, len(givenFuncs)) | ||
for name, fn := range givenFuncs { | ||
if name == "templatefile" { | ||
// We stub this one out to prevent recursive calls. | ||
funcs[name] = function.New(&function.Spec{ | ||
Params: []function.Parameter{ | ||
{ | ||
Name: "path", | ||
Type: cty.String, | ||
AllowMarked: true, | ||
}, | ||
{ | ||
Name: "vars", | ||
Type: cty.DynamicPseudoType, | ||
}, | ||
}, | ||
Type: func(args []cty.Value) (cty.Type, error) { | ||
return cty.NilType, fmt.Errorf("cannot recursively call templatefile from inside templatefile or templatestring") | ||
}, | ||
}) | ||
continue | ||
} | ||
funcs[name] = fn | ||
} | ||
ctx.Functions = funcs | ||
|
||
val, diags := expr.Value(ctx) | ||
if diags.HasErrors() { | ||
return cty.DynamicVal, diags | ||
} | ||
return val, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright (c) The OpenTofu Authors | ||
// SPDX-License-Identifier: MPL-2.0 | ||
// Copyright (c) 2023 HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package funcs | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/opentofu/opentofu/internal/lang/marks" | ||
"github.com/zclconf/go-cty/cty" | ||
"github.com/zclconf/go-cty/cty/function" | ||
) | ||
|
||
func TestRenderTemplate(t *testing.T) { | ||
tests := map[string]struct { | ||
Expr hcl.Expression | ||
Vars cty.Value | ||
Want cty.Value | ||
Err string | ||
}{ | ||
"String interpolation with variable": { | ||
hcl.StaticExpr(cty.StringVal("Hello, ${name}!"), hcl.Range{}), | ||
cty.MapVal(map[string]cty.Value{ | ||
"name": cty.StringVal("Jodie"), | ||
}), | ||
cty.StringVal("Hello, ${name}!"), | ||
``, | ||
}, | ||
"Looping through list": { | ||
hcl.StaticExpr(cty.StringVal("Items: %{ for x in list ~} ${x} %{ endfor ~}"), hcl.Range{}), | ||
cty.ObjectVal(map[string]cty.Value{ | ||
"list": cty.ListVal([]cty.Value{ | ||
cty.StringVal("a"), | ||
cty.StringVal("b"), | ||
cty.StringVal("c"), | ||
}), | ||
}), | ||
cty.StringVal("Items: %{ for x in list ~} ${x} %{ endfor ~}"), | ||
``, | ||
}, | ||
"Looping through map": { | ||
hcl.StaticExpr(cty.StringVal("%{ for key, value in list ~} ${key}:${value} %{ endfor ~}"), hcl.Range{}), | ||
cty.ObjectVal(map[string]cty.Value{ | ||
"list": cty.ObjectVal(map[string]cty.Value{ | ||
"item1": cty.StringVal("a"), | ||
"item2": cty.StringVal("b"), | ||
"item3": cty.StringVal("c"), | ||
}), | ||
}), | ||
cty.StringVal("%{ for key, value in list ~} ${key}:${value} %{ endfor ~}"), | ||
``, | ||
}, | ||
"Invalid template variable name": { | ||
hcl.StaticExpr(cty.StringVal("Hello, ${1}!"), hcl.Range{}), | ||
cty.MapVal(map[string]cty.Value{ | ||
"1": cty.StringVal("Jodie"), | ||
}), | ||
cty.NilVal, | ||
`invalid template variable name "1": must start with a letter, followed by zero or more letters, digits, and underscores`, | ||
}, | ||
"Interpolation of a boolean value": { | ||
hcl.StaticExpr(cty.StringVal("${val}"), hcl.Range{}), | ||
cty.ObjectVal(map[string]cty.Value{ | ||
"val": cty.True, | ||
}), | ||
cty.StringVal("${val}"), | ||
``, | ||
}, | ||
"Sensitive string template": { | ||
hcl.StaticExpr(cty.StringVal("My password is 1234").Mark(marks.Sensitive), hcl.Range{}), | ||
cty.EmptyObjectVal, | ||
cty.StringVal("My password is 1234").Mark(marks.Sensitive), | ||
``, | ||
}, | ||
"Sensitive template variable": { | ||
hcl.StaticExpr(cty.StringVal("My password is ${pass}"), hcl.Range{}), | ||
cty.ObjectVal(map[string]cty.Value{ | ||
"pass": cty.StringVal("secret").Mark(marks.Sensitive), | ||
}), | ||
cty.StringVal("My password is ${pass}"), | ||
``, | ||
}, | ||
} | ||
|
||
for name, test := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
|
||
got, err := renderTemplate(test.Expr, test.Vars, func() map[string]function.Function { | ||
return map[string]function.Function{} | ||
}) | ||
|
||
if err != nil { | ||
if test.Err == "" { | ||
t.Fatalf("unexpected error: %s", err) | ||
} else { | ||
if got, want := err.Error(), test.Err; got != want { | ||
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) | ||
} | ||
} | ||
} else if test.Err != "" { | ||
t.Fatal("succeeded; want error") | ||
} else { | ||
if !got.RawEquals(test.Want) { | ||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.