Skip to content

Commit f534b13

Browse files
authored
dyn: Allow interpolation for non-string primitive (#3522)
## Why Terraform supports that, so to match $resources resolution we need to support it as well. Also, it's natural for interpolation to support types that have string representation. ## Tests Existing unit test, new acceptance test for $var case. In my remote state PR, I have acc test for $resources case as well.
1 parent 429bedc commit f534b13

File tree

6 files changed

+75
-10
lines changed

6 files changed

+75
-10
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
variables:
2+
foo:
3+
default: 25
4+
bar:
5+
default: "${var.foo}+${var.foo}"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Local = true
2+
Cloud = false
3+
4+
[EnvMatrix]
5+
DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"bar": {
3+
"default": "25+25",
4+
"value": "25+25"
5+
},
6+
"foo": {
7+
"default": 25,
8+
"value": 25
9+
}
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$CLI bundle validate -o json | jq .variables

libs/dyn/dynvar/resolve.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,13 @@ func (r *resolver) resolveRef(ref Ref, seen []string) (dyn.Value, error) {
167167
// Try to turn the resolved value into a string.
168168
s, ok := resolved[j].AsString()
169169
if !ok {
170-
return dyn.InvalidValue, fmt.Errorf(
171-
"cannot interpolate non-string value: %s",
172-
ref.Matches[j][0],
173-
)
170+
// Only allow primitive types to be converted to string.
171+
switch resolved[j].Kind() {
172+
case dyn.KindString, dyn.KindBool, dyn.KindInt, dyn.KindFloat, dyn.KindTime, dyn.KindNil:
173+
s = fmt.Sprint(resolved[j].AsAny())
174+
default:
175+
return dyn.InvalidValue, fmt.Errorf("cannot interpolate non-primitive value of type %s into string", resolved[j].Kind())
176+
}
174177
}
175178

176179
ref.Str = strings.Replace(ref.Str, ref.Matches[j][0], s, 1)

libs/dyn/dynvar/resolve_test.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,56 @@ func TestResolveWithStringConcatenation(t *testing.T) {
107107
assert.Equal(t, "aba", getByPath(t, out, "c").MustString())
108108
}
109109

110-
func TestResolveWithTypeRetentionFailure(t *testing.T) {
110+
func TestResolveWithTypeInterpolation(t *testing.T) {
111111
in := dyn.V(map[string]dyn.Value{
112-
"a": dyn.V(1),
113-
"b": dyn.V(2),
114-
"c": dyn.V("${a} ${b}"),
112+
"a": dyn.V(1),
113+
"b": dyn.V(2),
114+
"c": dyn.V("${a} ${b}"),
115+
"float_val": dyn.V(3.14),
116+
"bool_true": dyn.V(true),
117+
"bool_false": dyn.V(false),
118+
"time_val": dyn.V(dyn.MustTime("2024-01-01")),
119+
"nil_val": dyn.NilValue,
120+
// Test interpolation of different types in string templates
121+
"float_interp": dyn.V("Value: ${float_val}"),
122+
"bool_true_interp": dyn.V("Enabled: ${bool_true}"),
123+
"bool_false_interp": dyn.V("Disabled: ${bool_false}"),
124+
"time_interp": dyn.V("Date: ${time_val}"),
125+
"nil_interp": dyn.V("Null: ${nil_val}"),
115126
})
116127

117-
_, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
118-
require.ErrorContains(t, err, "cannot interpolate non-string value: ${a}")
128+
out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in))
129+
require.NoError(t, err)
130+
131+
// Integer interpolation
132+
assert.EqualValues(t, "1 2", getByPath(t, out, "c").MustString())
133+
134+
// Float interpolation
135+
assert.EqualValues(t, "Value: 3.14", getByPath(t, out, "float_interp").MustString())
136+
137+
// Bool interpolation
138+
assert.EqualValues(t, "Enabled: true", getByPath(t, out, "bool_true_interp").MustString())
139+
assert.EqualValues(t, "Disabled: false", getByPath(t, out, "bool_false_interp").MustString())
140+
141+
// Time interpolation should convert to string representation of time.Time
142+
// Note: time.Time string representation includes timezone info
143+
timeResult := getByPath(t, out, "time_interp").MustString()
144+
assert.Contains(t, timeResult, "Date: 2024-01-01")
145+
assert.Contains(t, timeResult, "00:00:00")
146+
147+
// Nil interpolation
148+
assert.EqualValues(t, "Null: <nil>", getByPath(t, out, "nil_interp").MustString())
149+
}
150+
151+
func TestResolveWithTypeRetentionFailure(t *testing.T) {
152+
// Test that mapping interpolation fails with an error
153+
mappingTest := dyn.V(map[string]dyn.Value{
154+
"mapping": dyn.V(map[string]dyn.Value{"key": dyn.V("value")}),
155+
"interp": dyn.V("Config: ${mapping}"),
156+
})
157+
_, err := dynvar.Resolve(mappingTest, dynvar.DefaultLookup(mappingTest))
158+
require.Error(t, err)
159+
assert.Equal(t, "cannot interpolate non-primitive value of type map into string", err.Error())
119160
}
120161

121162
func TestResolveWithTypeRetention(t *testing.T) {

0 commit comments

Comments
 (0)