Merge pull request #3198 from rrjjvv/var-typing-no-value-fix

Consider typed, value-less variables to have `null` value
This commit is contained in:
CrazyMax
2025-05-21 16:34:42 +02:00
committed by GitHub
2 changed files with 78 additions and 35 deletions
+73 -28
View File
@@ -423,6 +423,63 @@ func TestHCLNullVariables(t *testing.T) {
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
}
func TestHCLTypedNullVariables(t *testing.T) {
types := []string{
"any",
"string", "number", "bool",
"list(string)", "set(string)", "map(string)",
"tuple([string])", "object({val: string})",
}
for _, varType := range types {
tName := fmt.Sprintf("variable typed %q with null default remains null", varType)
t.Run(tName, func(t *testing.T) {
dt := fmt.Sprintf(`
variable "FOO" {
type = %s
default = null
}
target "default" {
args = {
foo = equal(FOO, null)
}
}`, varType)
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
})
}
}
func TestHCLTypedValuelessVariables(t *testing.T) {
types := []string{
"any",
"string", "number", "bool",
"list(string)", "set(string)", "map(string)",
"tuple([string])", "object({val: string})",
}
for _, varType := range types {
tName := fmt.Sprintf("variable typed %q with no default is null", varType)
t.Run(tName, func(t *testing.T) {
dt := fmt.Sprintf(`
variable "FOO" {
type = %s
}
target "default" {
args = {
foo = equal(FOO, null)
}
}`, varType)
c, err := ParseFile([]byte(dt), "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
})
}
}
func TestJSONNullVariables(t *testing.T) {
dt := []byte(`{
"variable": {
@@ -1565,6 +1622,20 @@ target "two" {
require.Equal(t, map[string]*string{"b": ptrstr("pre-jkl")}, c.Targets[1].Args)
}
func TestEmptyVariable(t *testing.T) {
dt := []byte(`
variable "FOO" {}
target "default" {
args = {
foo = equal(FOO, "")
}
}`)
c, err := ParseFile(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
require.Equal(t, "true", *c.Targets[0].Args["foo"])
}
func TestEmptyVariableJSON(t *testing.T) {
dt := []byte(`{
"variable": {
@@ -1877,19 +1948,6 @@ func TestTypedVarOverrides(t *testing.T) {
override: `"hello"`,
wantValue: `"hello"`,
},
{
name: "any",
varType: "any",
override: "[1,2]",
wantValue: "[1,2]",
},
{
name: "any never convert to complex types",
varType: "any",
override: "[1,2]",
argValue: "length(FOO)",
wantErrorMsg: "collection must be a list",
},
{
name: "proper CSV list of strings",
varType: "list(string)",
@@ -2090,19 +2148,6 @@ func TestTypedVarOverrides_JSON(t *testing.T) {
override: `"hello"`,
wantValue: "hello",
},
{
name: "any",
varType: "any",
override: "[1,2]",
wantValue: "[1,2]",
},
{
name: "any never convert to complex types",
varType: "any",
override: "[1,2]",
argValue: "length(FOO)",
wantErrorMsg: "collection must be a list",
},
{
name: "list of strings",
varType: "list(string)",
@@ -2313,6 +2358,7 @@ func TestJSONOverridePriority(t *testing.T) {
dt := []byte(`
variable "foo" {
type = number
default = 101
}
target "default" {
@@ -2325,8 +2371,7 @@ func TestJSONOverridePriority(t *testing.T) {
c, err := ParseFile(dt, "docker-bake.hcl")
require.NoError(t, err)
require.Equal(t, 1, len(c.Targets))
// a variable with no value has always resulted in an empty string
require.Equal(t, "", *c.Targets[0].Args["bar"])
require.Equal(t, "101", *c.Targets[0].Args["bar"])
t.Setenv("foo_JSON", "42")
c, err = ParseFile(dt, "docker-bake.hcl")
+5 -7
View File
@@ -282,7 +282,7 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
}
var diags hcl.Diagnostics
varType := cty.DynamicPseudoType
varType, typeSpecified := cty.DynamicPseudoType, false
def, ok := p.attrs[name]
if !ok {
vr, ok := p.vars[name]
@@ -295,12 +295,13 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
if diags.HasErrors() {
return diags
}
typeSpecified = !varType.Equals(cty.DynamicPseudoType) || hcl.ExprAsKeyword(vr.Type) == "any"
}
if def == nil {
// lack of specified value is considered to have an empty string value,
// but any overrides get type checked
if _, ok, _ := p.valueHasOverride(name, false); !ok {
// Lack of specified value, when untyped is considered to have an empty string value.
// A typed variable with no value will result in (typed) nil.
if _, ok, _ := p.valueHasOverride(name, false); !ok && !typeSpecified {
vv := cty.StringVal("")
v = &vv
return
@@ -322,9 +323,6 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
}
}
// Not entirely true... this doesn't differentiate between a user that specified 'any'
// and a user that specified nothing. But the result is the same; both are treated as strings.
typeSpecified := !varType.Equals(cty.DynamicPseudoType)
envv, hasEnv, jsonEnv := p.valueHasOverride(name, typeSpecified)
_, isVar := p.vars[name]