hcl2shim: MockValueComposer handles structural-typed attributes (#2994)

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Martin Atkins
2025-07-14 04:13:20 -07:00
committed by Christian Mesh
parent c5fa034cfb
commit e69e22edb5
2 changed files with 137 additions and 4 deletions

View File

@@ -101,7 +101,17 @@ func (mvc MockValueComposer) composeMockValueForAttributes(schema *configschema.
// Non-computed attributes can't be generated
// so we set them from configuration only.
if !attr.Computed {
mockAttrs[k] = cty.NullVal(attr.Type)
if attr.NestedType != nil && attr.NestedType.Nesting == configschema.NestingGroup {
// This should not be possible to hit. Neither tofu or the provider framework will allow
// NestingGroup in here. However, this could change at some point and we want to be prepared for it.
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
fmt.Sprintf("Unsupported field `%v` in attribute mocking", k),
"Overriding non-computed fields is not allowed, so this field cannot be processed.",
))
continue
}
mockAttrs[k] = cty.NullVal(attr.ImpliedType())
if _, ok := defaults[k]; ok {
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
@@ -115,7 +125,7 @@ func (mvc MockValueComposer) composeMockValueForAttributes(schema *configschema.
// If the attribute is computed and not configured,
// we use provided value from defaults.
if ov, ok := defaults[k]; ok {
converted, err := convert.Convert(ov, attr.Type)
converted, err := convert.Convert(ov, attr.ImpliedType())
if err != nil {
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,

View File

@@ -3,8 +3,10 @@ package hcl2shim
import (
"testing"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/zclconf/go-cty-debug/ctydebug"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/configs/configschema"
)
// TestComposeMockValueBySchema ensures different configschema.Block values
@@ -177,6 +179,127 @@ func TestComposeMockValueBySchema(t *testing.T) {
}),
}),
},
"structural-attr-single": {
schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested": {
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.Number,
Computed: true,
},
},
},
},
},
},
config: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.DynamicPseudoType),
}),
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.Object(map[string]cty.Type{
"attr": cty.Number,
})),
}),
},
"structural-attr-group": {
schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested": {
NestedType: &configschema.Object{
Nesting: configschema.NestingGroup,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.Number,
Computed: true,
},
},
},
},
},
},
config: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.DynamicPseudoType),
}),
wantError: true, // This should not be hit in today's tofu/providers. If that assumption ever changes, we want to handle it gracefully.
},
"structural-attr-list": {
schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested": {
NestedType: &configschema.Object{
Nesting: configschema.NestingList,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.Number,
Computed: true,
},
},
},
},
},
},
config: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.DynamicPseudoType),
}),
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
"attr": cty.Number,
}))),
}),
},
"structural-attr-map": {
schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested": {
NestedType: &configschema.Object{
Nesting: configschema.NestingMap,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.Number,
Computed: true,
},
},
},
},
},
},
config: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.DynamicPseudoType),
}),
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
"attr": cty.Number,
}))),
}),
},
"structural-attr-set": {
schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested": {
NestedType: &configschema.Object{
Nesting: configschema.NestingSet,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.Number,
Computed: true,
},
},
},
},
},
},
config: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.DynamicPseudoType),
}),
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
"attr": cty.Number,
}))),
}),
},
"basic-group-block": {
schema: &configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
@@ -558,7 +681,7 @@ func TestComposeMockValueBySchema(t *testing.T) {
t.Fatalf("Got unexpected error diags: %v", gotDiags.ErrWithWarnings())
case !test.wantVal.RawEquals(gotVal):
t.Fatalf("Got unexpected value: %v", gotVal.GoString())
t.Fatalf("Wrong value\ngot: %swant: %sdiff: %s", ctydebug.ValueString(gotVal), ctydebug.ValueString(test.wantVal), ctydebug.DiffValues(test.wantVal, gotVal))
}
})
}