aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go
blob: 47a02565922899b2f9967d9f144aecf54f66a307 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package blocktoattr

import (
	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/terraform/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()
}