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:
+73
-28
@@ -423,6 +423,63 @@ func TestHCLNullVariables(t *testing.T) {
|
|||||||
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"])
|
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) {
|
func TestJSONNullVariables(t *testing.T) {
|
||||||
dt := []byte(`{
|
dt := []byte(`{
|
||||||
"variable": {
|
"variable": {
|
||||||
@@ -1565,6 +1622,20 @@ target "two" {
|
|||||||
require.Equal(t, map[string]*string{"b": ptrstr("pre-jkl")}, c.Targets[1].Args)
|
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) {
|
func TestEmptyVariableJSON(t *testing.T) {
|
||||||
dt := []byte(`{
|
dt := []byte(`{
|
||||||
"variable": {
|
"variable": {
|
||||||
@@ -1877,19 +1948,6 @@ func TestTypedVarOverrides(t *testing.T) {
|
|||||||
override: `"hello"`,
|
override: `"hello"`,
|
||||||
wantValue: `"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",
|
name: "proper CSV list of strings",
|
||||||
varType: "list(string)",
|
varType: "list(string)",
|
||||||
@@ -2090,19 +2148,6 @@ func TestTypedVarOverrides_JSON(t *testing.T) {
|
|||||||
override: `"hello"`,
|
override: `"hello"`,
|
||||||
wantValue: "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",
|
name: "list of strings",
|
||||||
varType: "list(string)",
|
varType: "list(string)",
|
||||||
@@ -2313,6 +2358,7 @@ func TestJSONOverridePriority(t *testing.T) {
|
|||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "foo" {
|
variable "foo" {
|
||||||
type = number
|
type = number
|
||||||
|
default = 101
|
||||||
}
|
}
|
||||||
|
|
||||||
target "default" {
|
target "default" {
|
||||||
@@ -2325,8 +2371,7 @@ func TestJSONOverridePriority(t *testing.T) {
|
|||||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
// a variable with no value has always resulted in an empty string
|
require.Equal(t, "101", *c.Targets[0].Args["bar"])
|
||||||
require.Equal(t, "", *c.Targets[0].Args["bar"])
|
|
||||||
|
|
||||||
t.Setenv("foo_JSON", "42")
|
t.Setenv("foo_JSON", "42")
|
||||||
c, err = ParseFile(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
varType := cty.DynamicPseudoType
|
varType, typeSpecified := cty.DynamicPseudoType, false
|
||||||
def, ok := p.attrs[name]
|
def, ok := p.attrs[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
vr, ok := p.vars[name]
|
vr, ok := p.vars[name]
|
||||||
@@ -295,12 +295,13 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
|
|||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
typeSpecified = !varType.Equals(cty.DynamicPseudoType) || hcl.ExprAsKeyword(vr.Type) == "any"
|
||||||
}
|
}
|
||||||
|
|
||||||
if def == nil {
|
if def == nil {
|
||||||
// lack of specified value is considered to have an empty string value,
|
// Lack of specified value, when untyped is considered to have an empty string value.
|
||||||
// but any overrides get type checked
|
// A typed variable with no value will result in (typed) nil.
|
||||||
if _, ok, _ := p.valueHasOverride(name, false); !ok {
|
if _, ok, _ := p.valueHasOverride(name, false); !ok && !typeSpecified {
|
||||||
vv := cty.StringVal("")
|
vv := cty.StringVal("")
|
||||||
v = &vv
|
v = &vv
|
||||||
return
|
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)
|
envv, hasEnv, jsonEnv := p.valueHasOverride(name, typeSpecified)
|
||||||
_, isVar := p.vars[name]
|
_, isVar := p.vars[name]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user