don't check MinItems with unknowns in blocks

If a block was defined via "dynamic", there will be only one block value
until the expansion is known. Since we can't detect dynamic blocks at
this point, don't verify MinItems while there are unknown values in the
config.

The decoder spec can also only check for existence of a block, so limit
the check to 0 or 1.
This commit is contained in:
James Bardin 2019-07-26 14:43:11 -07:00
parent 682286e184
commit 67dbd6d345
4 changed files with 80 additions and 6 deletions

View File

@ -113,7 +113,10 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
}
l := coll.LengthInt()
if l < blockS.MinItems {
// Assume that if there are unknowns this could have come from
// a dynamic block, and we can't validate MinItems yet.
if l < blockS.MinItems && coll.IsWhollyKnown() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
}
if l > blockS.MaxItems && blockS.MaxItems > 0 {
@ -161,7 +164,10 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
}
l := coll.LengthInt()
if l < blockS.MinItems {
// Assume that if there are unknowns this could have come from
// a dynamic block, and we can't validate MinItems yet.
if l < blockS.MinItems && coll.IsWhollyKnown() {
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
}
if l > blockS.MaxItems && blockS.MaxItems > 0 {

View File

@ -331,7 +331,7 @@ func TestCoerceValue(t *testing.T) {
"foo": {
Block: Block{},
Nesting: NestingList,
MinItems: 1,
MinItems: 2,
},
},
},
@ -345,6 +345,39 @@ func TestCoerceValue(t *testing.T) {
}),
"",
},
"unknowns in nested list": {
&Block{
BlockTypes: map[string]*NestedBlock{
"foo": {
Block: Block{
Attributes: map[string]*Attribute{
"attr": {
Type: cty.String,
Required: true,
},
},
},
Nesting: NestingList,
MinItems: 2,
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"attr": cty.UnknownVal(cty.String),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"attr": cty.UnknownVal(cty.String),
}),
}),
}),
"",
},
"unknown nested set": {
&Block{
Attributes: map[string]*Attribute{

View File

@ -33,6 +33,14 @@ func (b *Block) DecoderSpec() hcldec.Spec {
childSpec := blockS.Block.DecoderSpec()
// We can only validate 0 or 1 for MinItems, because a dynamic block
// may satisfy any number of min items while only having a single
// block in the config.
minItems := 0
if blockS.MinItems > 1 {
minItems = 1
}
switch blockS.Nesting {
case NestingSingle, NestingGroup:
ret[name] = &hcldec.BlockSpec{
@ -57,14 +65,14 @@ func (b *Block) DecoderSpec() hcldec.Spec {
ret[name] = &hcldec.BlockTupleSpec{
TypeName: name,
Nested: childSpec,
MinItems: blockS.MinItems,
MinItems: minItems,
MaxItems: blockS.MaxItems,
}
} else {
ret[name] = &hcldec.BlockListSpec{
TypeName: name,
Nested: childSpec,
MinItems: blockS.MinItems,
MinItems: minItems,
MaxItems: blockS.MaxItems,
}
}
@ -77,7 +85,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
ret[name] = &hcldec.BlockSetSpec{
TypeName: name,
Nested: childSpec,
MinItems: blockS.MinItems,
MinItems: minItems,
MaxItems: blockS.MaxItems,
}
case NestingMap:

View File

@ -356,6 +356,33 @@ func TestBlockDecoderSpec(t *testing.T) {
}),
1, // too many "foo" blocks
},
// dynamic blocks may fulfill MinItems, but there is only one block to
// decode.
"required MinItems": {
&Block{
BlockTypes: map[string]*NestedBlock{
"foo": {
Nesting: NestingList,
Block: Block{},
MinItems: 2,
},
},
},
hcltest.MockBody(&hcl.BodyContent{
Blocks: hcl.Blocks{
&hcl.Block{
Type: "foo",
Body: hcl.EmptyBody(),
},
},
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.EmptyObjectVal,
}),
}),
0,
},
"extraneous attribute": {
&Block{},
hcltest.MockBody(&hcl.BodyContent{