Skip to content

Commit 3301329

Browse files
committed
Ensure typed variables with no value still carry type
A value-less, untyped variable has always been converted to an empty string. The intention was that value-less, typed variables convert to a typed null, which was even specified in a code comment, but was never actually implemented. This resulted in a null value with a nil type. A value with a nil type cannot be coerced ("unified") with any other standard types. When this mismatch occurs, HCL attempts to return a diagnostic error, which in turn panics as the nil type is literally a nil pointer. Signed-off-by: Roberto Villarreal <rrjjvv@yahoo.com>
1 parent beaebcb commit 3301329

File tree

2 files changed

+80
-3
lines changed

2 files changed

+80
-3
lines changed

bake/hcl_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,79 @@ func TestHCLTypedValuelessVariables(t *testing.T) {
480480
}
481481
}
482482

483+
func TestHCLTypedValuelessVariablesUsedAsNull(t *testing.T) {
484+
// Omitting complex types since they can't (shouldn't) be used as direct build arg values.
485+
// Complex types can be used directly for other bake attributes, but that'll conflate HCL
486+
// evaluation with whether the attribute accepts null in lieu of empty list/object, etc.
487+
// This usage probably hits the 80/20 rule for usage of null variables.
488+
types := []string{
489+
"any",
490+
"string", "number", "bool",
491+
}
492+
493+
t.Run("assignment", func(t *testing.T) {
494+
for _, varType := range types {
495+
tName := fmt.Sprintf("value-less var typed %q", varType)
496+
t.Run(tName, func(t *testing.T) {
497+
dt := fmt.Sprintf(`
498+
variable "FOO" {
499+
type = %s
500+
}
501+
502+
target "default" {
503+
args = {
504+
foo = FOO
505+
}
506+
}`, varType)
507+
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
508+
require.NoError(t, err)
509+
require.Equal(t, 1, len(c.Targets))
510+
require.Nil(t, c.Targets[0].Args["foo"])
511+
})
512+
}
513+
})
514+
t.Run("ternary", func(t *testing.T) {
515+
for _, varType := range types {
516+
tName := fmt.Sprintf("value-less var of %q on 'false' branch", varType)
517+
t.Run(tName, func(t *testing.T) {
518+
dt := fmt.Sprintf(`
519+
variable "FOO" {
520+
type = %s
521+
}
522+
523+
target "default" {
524+
args = {
525+
foo = FOO == null ? "hi" : FOO
526+
}
527+
}`, varType)
528+
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
529+
require.NoError(t, err)
530+
require.Equal(t, 1, len(c.Targets))
531+
require.Equal(t, "hi", *c.Targets[0].Args["foo"])
532+
})
533+
}
534+
for _, varType := range types {
535+
tName := fmt.Sprintf("value-less var of %q on 'true' branch", varType)
536+
t.Run(tName, func(t *testing.T) {
537+
dt := fmt.Sprintf(`
538+
variable "FOO" {
539+
type = %s
540+
}
541+
542+
target "default" {
543+
args = {
544+
foo = FOO == null ? FOO : "hi"
545+
}
546+
}`, varType)
547+
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
548+
require.NoError(t, err)
549+
require.Equal(t, 1, len(c.Targets))
550+
require.Nil(t, c.Targets[0].Args["foo"])
551+
})
552+
}
553+
})
554+
}
555+
483556
func TestJSONNullVariables(t *testing.T) {
484557
dt := []byte(`{
485558
"variable": {

bake/hclparser/hclparser.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,21 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
304304
}
305305
}
306306

307+
var vv cty.Value
307308
if def == nil {
308309
// Lack of specified value, when untyped is considered to have an empty string value.
309310
// A typed variable with no value will result in (typed) nil.
310-
if _, ok, _ := p.valueHasOverride(name, false); !ok && !typeSpecified {
311-
vv := cty.StringVal("")
311+
if _, ok, _ := p.valueHasOverride(name, false); !ok {
312+
if typeSpecified {
313+
vv = cty.NullVal(varType)
314+
} else {
315+
vv = cty.StringVal("")
316+
}
312317
v = &vv
313318
return
314319
}
315320
}
316321

317-
var vv cty.Value
318322
if def != nil {
319323
if diags := p.loadDeps(ectx, def.Expr, nil, true); diags.HasErrors() {
320324
return diags

0 commit comments

Comments
 (0)