mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-16 19:52:49 -06:00
cdd9464f9a
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
147 lines
4.4 KiB
Go
147 lines
4.4 KiB
Go
package blocktoattr
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func ambiguousNames(schema *configschema.Block) map[string]struct{} {
|
|
if schema == nil {
|
|
return nil
|
|
}
|
|
ambiguousNames := make(map[string]struct{})
|
|
for name, attrS := range schema.Attributes {
|
|
aty := attrS.Type
|
|
if (aty.IsListType() || aty.IsSetType()) && aty.ElementType().IsObjectType() {
|
|
ambiguousNames[name] = struct{}{}
|
|
}
|
|
}
|
|
return ambiguousNames
|
|
}
|
|
|
|
func effectiveSchema(given *hcl.BodySchema, body hcl.Body, ambiguousNames map[string]struct{}, dynamicExpanded bool) *hcl.BodySchema {
|
|
ret := &hcl.BodySchema{}
|
|
|
|
appearsAsBlock := make(map[string]struct{})
|
|
{
|
|
// We'll construct some throwaway schemas here just to probe for
|
|
// whether each of our ambiguous names seems to be being used as
|
|
// an attribute or a block. We need to check both because in JSON
|
|
// syntax we rely on the schema to decide between attribute or block
|
|
// interpretation and so JSON will always answer yes to both of
|
|
// these questions and we want to prefer the attribute interpretation
|
|
// in that case.
|
|
var probeSchema hcl.BodySchema
|
|
|
|
for name := range ambiguousNames {
|
|
probeSchema = hcl.BodySchema{
|
|
Attributes: []hcl.AttributeSchema{
|
|
{
|
|
Name: name,
|
|
},
|
|
},
|
|
}
|
|
content, _, _ := body.PartialContent(&probeSchema)
|
|
if _, exists := content.Attributes[name]; exists {
|
|
// Can decode as an attribute, so we'll go with that.
|
|
continue
|
|
}
|
|
probeSchema = hcl.BodySchema{
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
{
|
|
Type: name,
|
|
},
|
|
},
|
|
}
|
|
content, _, _ = body.PartialContent(&probeSchema)
|
|
if len(content.Blocks) > 0 || dynamicExpanded {
|
|
// A dynamic block with an empty iterator returns nothing.
|
|
// If there's no attribute and we have either a block or a
|
|
// dynamic expansion, we need to rewrite this one as a
|
|
// block for a successful result.
|
|
appearsAsBlock[name] = struct{}{}
|
|
}
|
|
}
|
|
if !dynamicExpanded {
|
|
// If we're deciding for a context where dynamic blocks haven't
|
|
// been expanded yet then we need to probe for those too.
|
|
probeSchema = hcl.BodySchema{
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
{
|
|
Type: "dynamic",
|
|
LabelNames: []string{"type"},
|
|
},
|
|
},
|
|
}
|
|
content, _, _ := body.PartialContent(&probeSchema)
|
|
for _, block := range content.Blocks {
|
|
if _, exists := ambiguousNames[block.Labels[0]]; exists {
|
|
appearsAsBlock[block.Labels[0]] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, attrS := range given.Attributes {
|
|
if _, exists := appearsAsBlock[attrS.Name]; exists {
|
|
ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{
|
|
Type: attrS.Name,
|
|
})
|
|
} else {
|
|
ret.Attributes = append(ret.Attributes, attrS)
|
|
}
|
|
}
|
|
|
|
// Anything that is specified as a block type in the input schema remains
|
|
// that way by just passing through verbatim.
|
|
ret.Blocks = append(ret.Blocks, given.Blocks...)
|
|
|
|
return ret
|
|
}
|
|
|
|
// SchemaForCtyElementType converts a cty object type into an
|
|
// approximately-equivalent configschema.Block representing the element of
|
|
// a list or set. If the given type is not an object type then this
|
|
// function will panic.
|
|
func SchemaForCtyElementType(ty cty.Type) *configschema.Block {
|
|
atys := ty.AttributeTypes()
|
|
ret := &configschema.Block{
|
|
Attributes: make(map[string]*configschema.Attribute, len(atys)),
|
|
}
|
|
for name, aty := range atys {
|
|
ret.Attributes[name] = &configschema.Attribute{
|
|
Type: aty,
|
|
Optional: true,
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// SchemaForCtyContainerType converts a cty list-of-object or set-of-object type
|
|
// into an approximately-equivalent configschema.NestedBlock. If the given type
|
|
// is not of the expected kind then this function will panic.
|
|
func SchemaForCtyContainerType(ty cty.Type) *configschema.NestedBlock {
|
|
var nesting configschema.NestingMode
|
|
switch {
|
|
case ty.IsListType():
|
|
nesting = configschema.NestingList
|
|
case ty.IsSetType():
|
|
nesting = configschema.NestingSet
|
|
default:
|
|
panic("unsuitable type")
|
|
}
|
|
nested := SchemaForCtyElementType(ty.ElementType())
|
|
return &configschema.NestedBlock{
|
|
Nesting: nesting,
|
|
Block: *nested,
|
|
}
|
|
}
|
|
|
|
// TypeCanBeBlocks returns true if the given type is a list-of-object or
|
|
// set-of-object type, and would thus be subject to the blocktoattr fixup
|
|
// if used as an attribute type.
|
|
func TypeCanBeBlocks(ty cty.Type) bool {
|
|
return (ty.IsListType() || ty.IsSetType()) && ty.ElementType().IsObjectType()
|
|
}
|