Skip to content

Commit 2afa0a5

Browse files
authored
Merge pull request #29580 from hashicorp/jbardin/proposed-new-empty-containers
handle empty containers in ProposedNew NestedTypes
2 parents 332ea1f + 331dc8b commit 2afa0a5

File tree

4 files changed

+163
-34
lines changed

4 files changed

+163
-34
lines changed

internal/configs/configschema/coerce_value.go

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,19 @@ func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
2727
}
2828

2929
func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
30+
convType := b.specType()
31+
impliedType := convType.WithoutOptionalAttributesDeep()
32+
3033
switch {
3134
case in.IsNull():
32-
return cty.NullVal(b.ImpliedType()), nil
35+
return cty.NullVal(impliedType), nil
3336
case !in.IsKnown():
34-
return cty.UnknownVal(b.ImpliedType()), nil
37+
return cty.UnknownVal(impliedType), nil
3538
}
3639

3740
ty := in.Type()
3841
if !ty.IsObjectType() {
39-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
42+
return cty.UnknownVal(impliedType), path.NewErrorf("an object is required")
4043
}
4144

4245
for name := range ty.AttributeTypes() {
@@ -46,29 +49,32 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
4649
if _, defined := b.BlockTypes[name]; defined {
4750
continue
4851
}
49-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
52+
return cty.UnknownVal(impliedType), path.NewErrorf("unexpected attribute %q", name)
5053
}
5154

5255
attrs := make(map[string]cty.Value)
5356

5457
for name, attrS := range b.Attributes {
58+
attrType := impliedType.AttributeType(name)
59+
attrConvType := convType.AttributeType(name)
60+
5561
var val cty.Value
5662
switch {
5763
case ty.HasAttribute(name):
5864
val = in.GetAttr(name)
5965
case attrS.Computed || attrS.Optional:
60-
val = cty.NullVal(attrS.Type)
66+
val = cty.NullVal(attrType)
6167
default:
62-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
68+
return cty.UnknownVal(impliedType), path.NewErrorf("attribute %q is required", name)
6369
}
6470

65-
val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
71+
val, err := convert.Convert(val, attrConvType)
6672
if err != nil {
67-
return cty.UnknownVal(b.ImpliedType()), err
73+
return cty.UnknownVal(impliedType), append(path, cty.GetAttrStep{Name: name}).NewError(err)
6874
}
69-
7075
attrs[name] = val
7176
}
77+
7278
for typeName, blockS := range b.BlockTypes {
7379
switch blockS.Nesting {
7480

@@ -79,7 +85,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
7985
val := in.GetAttr(typeName)
8086
attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
8187
if err != nil {
82-
return cty.UnknownVal(b.ImpliedType()), err
88+
return cty.UnknownVal(impliedType), err
8389
}
8490
default:
8591
attrs[typeName] = blockS.EmptyValue()
@@ -100,7 +106,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
100106
}
101107

102108
if !coll.CanIterateElements() {
103-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
109+
return cty.UnknownVal(impliedType), path.NewErrorf("must be a list")
104110
}
105111
l := coll.LengthInt()
106112

@@ -116,7 +122,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
116122
idx, val := it.Element()
117123
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
118124
if err != nil {
119-
return cty.UnknownVal(b.ImpliedType()), err
125+
return cty.UnknownVal(impliedType), err
120126
}
121127
elems = append(elems, val)
122128
}
@@ -141,7 +147,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
141147
}
142148

143149
if !coll.CanIterateElements() {
144-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
150+
return cty.UnknownVal(impliedType), path.NewErrorf("must be a set")
145151
}
146152
l := coll.LengthInt()
147153

@@ -157,7 +163,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
157163
idx, val := it.Element()
158164
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
159165
if err != nil {
160-
return cty.UnknownVal(b.ImpliedType()), err
166+
return cty.UnknownVal(impliedType), err
161167
}
162168
elems = append(elems, val)
163169
}
@@ -182,7 +188,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
182188
}
183189

184190
if !coll.CanIterateElements() {
185-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
191+
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
186192
}
187193
l := coll.LengthInt()
188194
if l == 0 {
@@ -196,11 +202,11 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
196202
var err error
197203
key, val := it.Element()
198204
if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
199-
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
205+
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
200206
}
201207
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
202208
if err != nil {
203-
return cty.UnknownVal(b.ImpliedType()), err
209+
return cty.UnknownVal(impliedType), err
204210
}
205211
elems[key.AsString()] = val
206212
}
@@ -240,11 +246,3 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
240246

241247
return cty.ObjectVal(attrs), nil
242248
}
243-
244-
func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
245-
val, err := convert.Convert(in, a.Type)
246-
if err != nil {
247-
return cty.UnknownVal(a.Type), path.NewError(err)
248-
}
249-
return val, nil
250-
}

internal/configs/configschema/coerce_value_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,67 @@ func TestCoerceValue(t *testing.T) {
538538
}),
539539
``,
540540
},
541+
"nested types": {
542+
// handle NestedTypes
543+
&Block{
544+
Attributes: map[string]*Attribute{
545+
"foo": {
546+
NestedType: &Object{
547+
Nesting: NestingList,
548+
Attributes: map[string]*Attribute{
549+
"bar": {
550+
Type: cty.String,
551+
Required: true,
552+
},
553+
"baz": {
554+
Type: cty.Map(cty.String),
555+
Optional: true,
556+
},
557+
},
558+
},
559+
Optional: true,
560+
},
561+
"fob": {
562+
NestedType: &Object{
563+
Nesting: NestingSet,
564+
Attributes: map[string]*Attribute{
565+
"bar": {
566+
Type: cty.String,
567+
Optional: true,
568+
},
569+
},
570+
},
571+
Optional: true,
572+
},
573+
},
574+
},
575+
cty.ObjectVal(map[string]cty.Value{
576+
"foo": cty.ListVal([]cty.Value{
577+
cty.ObjectVal(map[string]cty.Value{
578+
"bar": cty.StringVal("beep"),
579+
}),
580+
cty.ObjectVal(map[string]cty.Value{
581+
"bar": cty.StringVal("boop"),
582+
}),
583+
}),
584+
}),
585+
cty.ObjectVal(map[string]cty.Value{
586+
"foo": cty.ListVal([]cty.Value{
587+
cty.ObjectVal(map[string]cty.Value{
588+
"bar": cty.StringVal("beep"),
589+
"baz": cty.NullVal(cty.Map(cty.String)),
590+
}),
591+
cty.ObjectVal(map[string]cty.Value{
592+
"bar": cty.StringVal("boop"),
593+
"baz": cty.NullVal(cty.Map(cty.String)),
594+
}),
595+
}),
596+
"fob": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
597+
"bar": cty.String,
598+
}))),
599+
}),
600+
``,
601+
},
541602
}
542603

543604
for name, test := range tests {

internal/plans/objchange/objchange.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,9 @@ func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, conf
308308
}
309309

310310
func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value) cty.Value {
311-
var newV cty.Value
311+
// If the config is null or empty, we will be using this default value.
312+
newV := config
313+
312314
switch schema.Nesting {
313315
case configschema.NestingSingle:
314316
if !config.IsNull() {
@@ -323,6 +325,7 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
323325
if config.IsKnown() && !config.IsNull() {
324326
configVLen = config.LengthInt()
325327
}
328+
326329
if configVLen > 0 {
327330
newVals := make([]cty.Value, 0, configVLen)
328331
for it := config.ElementIterator(); it.Next(); {
@@ -345,8 +348,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
345348
} else {
346349
newV = cty.ListVal(newVals)
347350
}
348-
} else {
349-
newV = cty.NullVal(schema.ImpliedType())
350351
}
351352

352353
case configschema.NestingMap:
@@ -378,8 +379,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
378379
// object values so that elements might have different types
379380
// in case of dynamically-typed attributes.
380381
newV = cty.ObjectVal(newVals)
381-
} else {
382-
newV = cty.NullVal(schema.ImpliedType())
383382
}
384383
} else {
385384
configVLen := 0
@@ -403,8 +402,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
403402
newVals[k] = cty.ObjectVal(newEV)
404403
}
405404
newV = cty.MapVal(newVals)
406-
} else {
407-
newV = cty.NullVal(schema.ImpliedType())
408405
}
409406
}
410407

@@ -446,8 +443,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
446443
}
447444
}
448445
newV = cty.SetVal(newVals)
449-
} else {
450-
newV = cty.NullVal(schema.ImpliedType())
451446
}
452447
}
453448

internal/plans/objchange/objchange_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,81 @@ func TestProposedNew(t *testing.T) {
14611461
}))),
14621462
}),
14631463
},
1464+
"expected empty NestedTypes": {
1465+
&configschema.Block{
1466+
Attributes: map[string]*configschema.Attribute{
1467+
"set": {
1468+
NestedType: &configschema.Object{
1469+
Nesting: configschema.NestingSet,
1470+
Attributes: map[string]*configschema.Attribute{
1471+
"bar": {Type: cty.String},
1472+
},
1473+
},
1474+
Optional: true,
1475+
},
1476+
"map": {
1477+
NestedType: &configschema.Object{
1478+
Nesting: configschema.NestingMap,
1479+
Attributes: map[string]*configschema.Attribute{
1480+
"bar": {Type: cty.String},
1481+
},
1482+
},
1483+
Optional: true,
1484+
},
1485+
},
1486+
},
1487+
cty.ObjectVal(map[string]cty.Value{
1488+
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1489+
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1490+
}),
1491+
cty.ObjectVal(map[string]cty.Value{
1492+
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1493+
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1494+
}),
1495+
cty.ObjectVal(map[string]cty.Value{
1496+
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1497+
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
1498+
}),
1499+
},
1500+
"optional types set replacement": {
1501+
&configschema.Block{
1502+
Attributes: map[string]*configschema.Attribute{
1503+
"set": {
1504+
NestedType: &configschema.Object{
1505+
Nesting: configschema.NestingSet,
1506+
Attributes: map[string]*configschema.Attribute{
1507+
"bar": {
1508+
Type: cty.String,
1509+
Required: true,
1510+
},
1511+
},
1512+
},
1513+
Optional: true,
1514+
},
1515+
},
1516+
},
1517+
cty.ObjectVal(map[string]cty.Value{
1518+
"set": cty.SetVal([]cty.Value{
1519+
cty.ObjectVal(map[string]cty.Value{
1520+
"bar": cty.StringVal("old"),
1521+
}),
1522+
}),
1523+
}),
1524+
cty.ObjectVal(map[string]cty.Value{
1525+
"set": cty.SetVal([]cty.Value{
1526+
cty.ObjectVal(map[string]cty.Value{
1527+
"bar": cty.StringVal("new"),
1528+
}),
1529+
}),
1530+
}),
1531+
cty.ObjectVal(map[string]cty.Value{
1532+
"set": cty.SetVal([]cty.Value{
1533+
cty.ObjectVal(map[string]cty.Value{
1534+
"bar": cty.StringVal("new"),
1535+
}),
1536+
}),
1537+
}),
1538+
},
14641539
}
14651540

14661541
for name, test := range tests {

0 commit comments

Comments
 (0)