6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/zclconf/go-cty/cty"
8 "github.com/zclconf/go-cty/cty/convert"
11 type expandSpec struct {
13 blockTypeRange hcl.Range
17 labelExprs []hcl.Expression
19 inherited map[string]*iteration
22 func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Block) (*expandSpec, hcl.Diagnostics) {
23 var diags hcl.Diagnostics
25 var schema *hcl.BodySchema
26 if len(blockS.LabelNames) != 0 {
27 schema = dynamicBlockBodySchemaLabels
29 schema = dynamicBlockBodySchemaNoLabels
32 specContent, specDiags := rawSpec.Body.Content(schema)
33 diags = append(diags, specDiags...)
34 if specDiags.HasErrors() {
38 //// for_each attribute
40 eachAttr := specContent.Attributes["for_each"]
41 eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx)
42 diags = append(diags, eachDiags...)
44 if !eachVal.CanIterateElements() && eachVal.Type() != cty.DynamicPseudoType {
45 // We skip this error for DynamicPseudoType because that means we either
46 // have a null (which is checked immediately below) or an unknown
47 // (which is handled in the expandBody Content methods).
48 diags = append(diags, &hcl.Diagnostic{
49 Severity: hcl.DiagError,
50 Summary: "Invalid dynamic for_each value",
51 Detail: fmt.Sprintf("Cannot use a %s value in for_each. An iterable collection is required.", eachVal.Type().FriendlyName()),
52 Subject: eachAttr.Expr.Range().Ptr(),
53 Expression: eachAttr.Expr,
54 EvalContext: b.forEachCtx,
59 diags = append(diags, &hcl.Diagnostic{
60 Severity: hcl.DiagError,
61 Summary: "Invalid dynamic for_each value",
62 Detail: "Cannot use a null value in for_each.",
63 Subject: eachAttr.Expr.Range().Ptr(),
64 Expression: eachAttr.Expr,
65 EvalContext: b.forEachCtx,
70 //// iterator attribute
72 iteratorName := blockS.Type
73 if iteratorAttr := specContent.Attributes["iterator"]; iteratorAttr != nil {
74 itTraversal, itDiags := hcl.AbsTraversalForExpr(iteratorAttr.Expr)
75 diags = append(diags, itDiags...)
76 if itDiags.HasErrors() {
80 if len(itTraversal) != 1 {
81 diags = append(diags, &hcl.Diagnostic{
82 Severity: hcl.DiagError,
83 Summary: "Invalid dynamic iterator name",
84 Detail: "Dynamic iterator must be a single variable name.",
85 Subject: itTraversal.SourceRange().Ptr(),
90 iteratorName = itTraversal.RootName()
93 var labelExprs []hcl.Expression
94 if labelsAttr := specContent.Attributes["labels"]; labelsAttr != nil {
95 var labelDiags hcl.Diagnostics
96 labelExprs, labelDiags = hcl.ExprList(labelsAttr.Expr)
97 diags = append(diags, labelDiags...)
98 if labelDiags.HasErrors() {
102 if len(labelExprs) > len(blockS.LabelNames) {
103 diags = append(diags, &hcl.Diagnostic{
104 Severity: hcl.DiagError,
105 Summary: "Extraneous dynamic block label",
106 Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)),
107 Subject: labelExprs[len(blockS.LabelNames)].Range().Ptr(),
110 } else if len(labelExprs) < len(blockS.LabelNames) {
111 diags = append(diags, &hcl.Diagnostic{
112 Severity: hcl.DiagError,
113 Summary: "Insufficient dynamic block labels",
114 Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)),
115 Subject: labelsAttr.Expr.Range().Ptr(),
121 // Since our schema requests only blocks of type "content", we can assume
122 // that all entries in specContent.Blocks are content blocks.
123 if len(specContent.Blocks) == 0 {
124 diags = append(diags, &hcl.Diagnostic{
125 Severity: hcl.DiagError,
126 Summary: "Missing dynamic content block",
127 Detail: "A dynamic block must have a nested block of type \"content\" to describe the body of each generated block.",
128 Subject: &specContent.MissingItemRange,
132 if len(specContent.Blocks) > 1 {
133 diags = append(diags, &hcl.Diagnostic{
134 Severity: hcl.DiagError,
135 Summary: "Extraneous dynamic content block",
136 Detail: "Only one nested content block is allowed for each dynamic block.",
137 Subject: &specContent.Blocks[1].DefRange,
143 blockType: blockS.Type,
144 blockTypeRange: rawSpec.LabelRanges[0],
145 defRange: rawSpec.DefRange,
147 iteratorName: iteratorName,
148 labelExprs: labelExprs,
149 contentBody: specContent.Blocks[0].Body,
153 func (s *expandSpec) newBlock(i *iteration, ctx *hcl.EvalContext) (*hcl.Block, hcl.Diagnostics) {
154 var diags hcl.Diagnostics
156 var labelRanges []hcl.Range
157 lCtx := i.EvalContext(ctx)
158 for _, labelExpr := range s.labelExprs {
159 labelVal, labelDiags := labelExpr.Value(lCtx)
160 diags = append(diags, labelDiags...)
161 if labelDiags.HasErrors() {
166 labelVal, convErr = convert.Convert(labelVal, cty.String)
168 diags = append(diags, &hcl.Diagnostic{
169 Severity: hcl.DiagError,
170 Summary: "Invalid dynamic block label",
171 Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr),
172 Subject: labelExpr.Range().Ptr(),
173 Expression: labelExpr,
178 if labelVal.IsNull() {
179 diags = append(diags, &hcl.Diagnostic{
180 Severity: hcl.DiagError,
181 Summary: "Invalid dynamic block label",
182 Detail: "Cannot use a null value as a dynamic block label.",
183 Subject: labelExpr.Range().Ptr(),
184 Expression: labelExpr,
189 if !labelVal.IsKnown() {
190 diags = append(diags, &hcl.Diagnostic{
191 Severity: hcl.DiagError,
192 Summary: "Invalid dynamic block label",
193 Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.",
194 Subject: labelExpr.Range().Ptr(),
195 Expression: labelExpr,
201 labels = append(labels, labelVal.AsString())
202 labelRanges = append(labelRanges, labelExpr.Range())
207 TypeRange: s.blockTypeRange,
209 LabelRanges: labelRanges,
210 DefRange: s.defRange,