mirror of https://github.com/docker/buildx.git
Merge pull request #2794 from crazy-max/bake-var-req
bake: basic variable validation
This commit is contained in:
commit
a6ef9db84d
|
@ -1856,3 +1856,155 @@ func TestNetNone(t *testing.T) {
|
|||
require.Len(t, bo["app"].Allow, 0)
|
||||
require.Equal(t, "none", bo["app"].NetworkMode)
|
||||
}
|
||||
|
||||
func TestVariableValidation(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(`
|
||||
variable "FOO" {
|
||||
validation {
|
||||
condition = FOO != ""
|
||||
error_message = "FOO is required."
|
||||
}
|
||||
}
|
||||
target "app" {
|
||||
args = {
|
||||
FOO = FOO
|
||||
}
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Setenv("FOO", "bar")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "FOO is required.")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariableValidationMulti(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(`
|
||||
variable "FOO" {
|
||||
validation {
|
||||
condition = FOO != ""
|
||||
error_message = "FOO is required."
|
||||
}
|
||||
validation {
|
||||
condition = strlen(FOO) > 4
|
||||
error_message = "FOO must be longer than 4 characters."
|
||||
}
|
||||
}
|
||||
target "app" {
|
||||
args = {
|
||||
FOO = FOO
|
||||
}
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Setenv("FOO", "barbar")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("InvalidLength", func(t *testing.T) {
|
||||
t.Setenv("FOO", "bar")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "FOO must be longer than 4 characters.")
|
||||
})
|
||||
|
||||
t.Run("InvalidEmpty", func(t *testing.T) {
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "FOO is required.")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariableValidationWithDeps(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(`
|
||||
variable "FOO" {}
|
||||
variable "BAR" {
|
||||
validation {
|
||||
condition = FOO != ""
|
||||
error_message = "BAR requires FOO to be set."
|
||||
}
|
||||
}
|
||||
target "app" {
|
||||
args = {
|
||||
BAR = BAR
|
||||
}
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Setenv("FOO", "bar")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("SetBar", func(t *testing.T) {
|
||||
t.Setenv("FOO", "bar")
|
||||
t.Setenv("BAR", "baz")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "BAR requires FOO to be set.")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariableValidationTyped(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(`
|
||||
variable "FOO" {
|
||||
default = 0
|
||||
validation {
|
||||
condition = FOO > 5
|
||||
error_message = "FOO must be greater than 5."
|
||||
}
|
||||
}
|
||||
target "app" {
|
||||
args = {
|
||||
FOO = FOO
|
||||
}
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Setenv("FOO", "10")
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "FOO must be greater than 5.")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,11 +25,17 @@ type Opt struct {
|
|||
}
|
||||
|
||||
type variable struct {
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
Default *hcl.Attribute `json:"default,omitempty" hcl:"default,optional"`
|
||||
Description string `json:"description,omitempty" hcl:"description,optional"`
|
||||
Body hcl.Body `json:"-" hcl:",body"`
|
||||
Remain hcl.Body `json:"-" hcl:",remain"`
|
||||
Name string `json:"-" hcl:"name,label"`
|
||||
Default *hcl.Attribute `json:"default,omitempty" hcl:"default,optional"`
|
||||
Description string `json:"description,omitempty" hcl:"description,optional"`
|
||||
Validations []*variableValidation `json:"validation,omitempty" hcl:"validation,block"`
|
||||
Body hcl.Body `json:"-" hcl:",body"`
|
||||
Remain hcl.Body `json:"-" hcl:",remain"`
|
||||
}
|
||||
|
||||
type variableValidation struct {
|
||||
Condition hcl.Expression `json:"condition" hcl:"condition"`
|
||||
ErrorMessage hcl.Expression `json:"error_message" hcl:"error_message"`
|
||||
}
|
||||
|
||||
type functionDef struct {
|
||||
|
@ -541,6 +547,33 @@ func (p *parser) resolveBlockNames(block *hcl.Block) ([]string, error) {
|
|||
return names, nil
|
||||
}
|
||||
|
||||
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)
|
||||
if condDiags.HasErrors() {
|
||||
diags = append(diags, condDiags...)
|
||||
continue
|
||||
}
|
||||
if !condition.True() {
|
||||
message, msgDiags := validation.ErrorMessage.Value(ectx)
|
||||
if msgDiags.HasErrors() {
|
||||
diags = append(diags, msgDiags...)
|
||||
continue
|
||||
}
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Validation failed",
|
||||
Detail: message.AsString(),
|
||||
Subject: validation.Condition.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
Description string
|
||||
|
@ -686,6 +719,9 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (*ParseMeta, hcl.Diagnostics) {
|
|||
}
|
||||
vars = append(vars, v)
|
||||
}
|
||||
if diags := p.validateVariables(p.vars, p.ectx); diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
for k := range p.funcs {
|
||||
if err := p.resolveFunction(p.ectx, k); err != nil {
|
||||
|
|
Loading…
Reference in New Issue