diff --git a/bake/bake_test.go b/bake/bake_test.go index d946cb1c..03bc532d 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -2142,6 +2142,73 @@ target "app" { }) } +func TestVariableValidationConditionNull(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte(` +variable "PORT" { + default = 3000 + validation {} +} +target "app" { + args = { + PORT = PORT + } +} +`), + } + + _, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{}) + require.Error(t, err) + require.Contains(t, err.Error(), "Condition expression must return either true or false, not null") +} + +func TestVariableValidationConditionUnknownValue(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte(` +variable "PORT" { + default = 3000 + validation { + condition = "foo" + } +} +target "app" { + args = { + PORT = PORT + } +} +`), + } + + _, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{}) + require.Error(t, err) + require.Contains(t, err.Error(), "Invalid condition result value: a bool is required") +} + +func TestVariableValidationInvalidErrorMessage(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte(` +variable "FOO" { + default = 0 + validation { + condition = FOO > 5 + } +} +target "app" { + args = { + FOO = FOO + } +} +`), + } + + _, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{}) + require.Error(t, err) + require.Contains(t, err.Error(), "This check failed, but has an invalid error message") +} + // https://github.com/docker/buildx/issues/2822 func TestVariableEmpty(t *testing.T) { fp := File{ diff --git a/bake/hclparser/hclparser.go b/bake/hclparser/hclparser.go index 6f98cfb6..6aa5eada 100644 --- a/bake/hclparser/hclparser.go +++ b/bake/hclparser/hclparser.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/pkg/errors" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/convert" ) type Opt struct { @@ -555,27 +556,57 @@ func (p *parser) resolveBlockNames(block *hcl.Block) ([]string, error) { func (p *parser) validateVariables(vars map[string]*variable, ectx *hcl.EvalContext) hcl.Diagnostics { var diags hcl.Diagnostics for _, v := range vars { - for _, validation := range v.Validations { - condition, condDiags := validation.Condition.Value(ectx) + for _, rule := range v.Validations { + resultVal, condDiags := rule.Condition.Value(ectx) if condDiags.HasErrors() { diags = append(diags, condDiags...) continue } - if !condition.True() { - message, msgDiags := validation.ErrorMessage.Value(ectx) + + if resultVal.IsNull() { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid condition result", + Detail: "Condition expression must return either true or false, not null.", + Subject: rule.Condition.Range().Ptr(), + Expression: rule.Condition, + }) + continue + } + + var err error + resultVal, err = convert.Convert(resultVal, cty.Bool) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid condition result", + Detail: fmt.Sprintf("Invalid condition result value: %s", err), + Subject: rule.Condition.Range().Ptr(), + Expression: rule.Condition, + }) + continue + } + + if !resultVal.True() { + message, msgDiags := rule.ErrorMessage.Value(ectx) if msgDiags.HasErrors() { diags = append(diags, msgDiags...) continue } + errorMessage := "This check failed, but has an invalid error message." + if !message.IsNull() { + errorMessage = message.AsString() + } diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Validation failed", - Detail: message.AsString(), - Subject: validation.Condition.Range().Ptr(), + Detail: errorMessage, + Subject: rule.Condition.Range().Ptr(), }) } } } + return diags }