github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/expand_body.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package dynblock
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  // expandBody wraps another hcl.Body and expands any "dynamic" blocks found
    14  // inside whenever Content or PartialContent is called.
    15  type expandBody struct {
    16  	original   hcl.Body
    17  	forEachCtx *hcl.EvalContext
    18  	iteration  *iteration // non-nil if we're nested inside another "dynamic" block
    19  
    20  	checkForEach []func(cty.Value, hcl.Expression, *hcl.EvalContext) hcl.Diagnostics
    21  
    22  	// These are used with PartialContent to produce a "remaining items"
    23  	// body to return. They are nil on all bodies fresh out of the transformer.
    24  	//
    25  	// Note that this is re-implemented here rather than delegating to the
    26  	// existing support required by the underlying body because we need to
    27  	// retain access to the entire original body on subsequent decode operations
    28  	// so we can retain any "dynamic" blocks for types we didn't take consume
    29  	// on the first pass.
    30  	hiddenAttrs  map[string]struct{}
    31  	hiddenBlocks map[string]hcl.BlockHeaderSchema
    32  }
    33  
    34  func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
    35  	extSchema := b.extendSchema(schema)
    36  	rawContent, diags := b.original.Content(extSchema)
    37  
    38  	blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false)
    39  	diags = append(diags, blockDiags...)
    40  	attrs := b.prepareAttributes(rawContent.Attributes)
    41  
    42  	content := &hcl.BodyContent{
    43  		Attributes:       attrs,
    44  		Blocks:           blocks,
    45  		MissingItemRange: b.original.MissingItemRange(),
    46  	}
    47  
    48  	return content, diags
    49  }
    50  
    51  func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
    52  	extSchema := b.extendSchema(schema)
    53  	rawContent, _, diags := b.original.PartialContent(extSchema)
    54  	// We discard the "remain" argument above because we're going to construct
    55  	// our own remain that also takes into account remaining "dynamic" blocks.
    56  
    57  	blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true)
    58  	diags = append(diags, blockDiags...)
    59  	attrs := b.prepareAttributes(rawContent.Attributes)
    60  
    61  	content := &hcl.BodyContent{
    62  		Attributes:       attrs,
    63  		Blocks:           blocks,
    64  		MissingItemRange: b.original.MissingItemRange(),
    65  	}
    66  
    67  	remain := &expandBody{
    68  		original:     b.original,
    69  		forEachCtx:   b.forEachCtx,
    70  		iteration:    b.iteration,
    71  		checkForEach: b.checkForEach,
    72  		hiddenAttrs:  make(map[string]struct{}),
    73  		hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
    74  	}
    75  	for name := range b.hiddenAttrs {
    76  		remain.hiddenAttrs[name] = struct{}{}
    77  	}
    78  	for typeName, blockS := range b.hiddenBlocks {
    79  		remain.hiddenBlocks[typeName] = blockS
    80  	}
    81  	for _, attrS := range schema.Attributes {
    82  		remain.hiddenAttrs[attrS.Name] = struct{}{}
    83  	}
    84  	for _, blockS := range schema.Blocks {
    85  		remain.hiddenBlocks[blockS.Type] = blockS
    86  	}
    87  
    88  	return content, remain, diags
    89  }
    90  
    91  func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
    92  	// We augment the requested schema to also include our special "dynamic"
    93  	// block type, since then we'll get instances of it interleaved with
    94  	// all of the literal child blocks we must also include.
    95  	extSchema := &hcl.BodySchema{
    96  		Attributes: schema.Attributes,
    97  		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1),
    98  	}
    99  	copy(extSchema.Blocks, schema.Blocks)
   100  	extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
   101  
   102  	// If we have any hiddenBlocks then we also need to register those here
   103  	// so that a call to "Content" on the underlying body won't fail.
   104  	// (We'll filter these out again once we process the result of either
   105  	// Content or PartialContent.)
   106  	for _, blockS := range b.hiddenBlocks {
   107  		extSchema.Blocks = append(extSchema.Blocks, blockS)
   108  	}
   109  
   110  	// If we have any hiddenAttrs then we also need to register these, for
   111  	// the same reason as we deal with hiddenBlocks above.
   112  	if len(b.hiddenAttrs) != 0 {
   113  		newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs))
   114  		copy(newAttrs, extSchema.Attributes)
   115  		for name := range b.hiddenAttrs {
   116  			newAttrs = append(newAttrs, hcl.AttributeSchema{
   117  				Name:     name,
   118  				Required: false,
   119  			})
   120  		}
   121  		extSchema.Attributes = newAttrs
   122  	}
   123  
   124  	return extSchema
   125  }
   126  
   127  func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes {
   128  	if len(b.hiddenAttrs) == 0 && b.iteration == nil {
   129  		// Easy path: just pass through the attrs from the original body verbatim
   130  		return rawAttrs
   131  	}
   132  
   133  	// Otherwise we have some work to do: we must filter out any attributes
   134  	// that are hidden (since a previous PartialContent call already saw these)
   135  	// and wrap the expressions of the inner attributes so that they will
   136  	// have access to our iteration variables.
   137  	attrs := make(hcl.Attributes, len(rawAttrs))
   138  	for name, rawAttr := range rawAttrs {
   139  		if _, hidden := b.hiddenAttrs[name]; hidden {
   140  			continue
   141  		}
   142  		if b.iteration != nil {
   143  			attr := *rawAttr // shallow copy so we can mutate it
   144  			attr.Expr = exprWrap{
   145  				Expression: attr.Expr,
   146  				i:          b.iteration,
   147  			}
   148  			attrs[name] = &attr
   149  		} else {
   150  			// If we have no active iteration then no wrapping is required.
   151  			attrs[name] = rawAttr
   152  		}
   153  	}
   154  	return attrs
   155  }
   156  
   157  func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) {
   158  	var blocks hcl.Blocks
   159  	var diags hcl.Diagnostics
   160  
   161  	for _, rawBlock := range rawBlocks {
   162  		switch rawBlock.Type {
   163  		case "dynamic":
   164  			realBlockType := rawBlock.Labels[0]
   165  			if _, hidden := b.hiddenBlocks[realBlockType]; hidden {
   166  				continue
   167  			}
   168  
   169  			var blockS *hcl.BlockHeaderSchema
   170  			for _, candidate := range schema.Blocks {
   171  				if candidate.Type == realBlockType {
   172  					blockS = &candidate
   173  					break
   174  				}
   175  			}
   176  			if blockS == nil {
   177  				// Not a block type that the caller requested.
   178  				if !partial {
   179  					diags = append(diags, &hcl.Diagnostic{
   180  						Severity: hcl.DiagError,
   181  						Summary:  "Unsupported block type",
   182  						Detail:   fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType),
   183  						Subject:  &rawBlock.LabelRanges[0],
   184  					})
   185  				}
   186  				continue
   187  			}
   188  
   189  			spec, specDiags := b.decodeSpec(blockS, rawBlock)
   190  			diags = append(diags, specDiags...)
   191  			if specDiags.HasErrors() {
   192  				continue
   193  			}
   194  
   195  			if spec.forEachVal.IsKnown() {
   196  				for it := spec.forEachVal.ElementIterator(); it.Next(); {
   197  					key, value := it.Element()
   198  					i := b.iteration.MakeChild(spec.iteratorName, key, value)
   199  
   200  					block, blockDiags := spec.newBlock(i, b.forEachCtx)
   201  					diags = append(diags, blockDiags...)
   202  					if block != nil {
   203  						// Attach our new iteration context so that attributes
   204  						// and other nested blocks can refer to our iterator.
   205  						block.Body = b.expandChild(block.Body, i)
   206  						blocks = append(blocks, block)
   207  					}
   208  				}
   209  			} else {
   210  				// If our top-level iteration value isn't known then we
   211  				// substitute an unknownBody, which will cause the entire block
   212  				// to evaluate to an unknown value.
   213  				i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
   214  				block, blockDiags := spec.newBlock(i, b.forEachCtx)
   215  				diags = append(diags, blockDiags...)
   216  				if block != nil {
   217  					block.Body = unknownBody{b.expandChild(block.Body, i)}
   218  					blocks = append(blocks, block)
   219  				}
   220  			}
   221  
   222  		default:
   223  			if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden {
   224  				// A static block doesn't create a new iteration context, but
   225  				// it does need to inherit _our own_ iteration context in
   226  				// case it contains expressions that refer to our inherited
   227  				// iterators, or nested "dynamic" blocks.
   228  				expandedBlock := *rawBlock // shallow copy
   229  				expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration)
   230  				blocks = append(blocks, &expandedBlock)
   231  			}
   232  		}
   233  	}
   234  
   235  	return blocks, diags
   236  }
   237  
   238  func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
   239  	chiCtx := i.EvalContext(b.forEachCtx)
   240  	ret := Expand(child, chiCtx)
   241  	ret.(*expandBody).iteration = i
   242  	ret.(*expandBody).checkForEach = b.checkForEach
   243  	return ret
   244  }
   245  
   246  func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
   247  	// blocks aren't allowed in JustAttributes mode and this body can
   248  	// only produce blocks, so we'll just pass straight through to our
   249  	// underlying body here.
   250  	return b.original.JustAttributes()
   251  }
   252  
   253  func (b *expandBody) MissingItemRange() hcl.Range {
   254  	return b.original.MissingItemRange()
   255  }