buildr/internal/hcl/for_each.go
Peter e60726ef9e
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
feat: implement new and man for plugin modules
- use extracted shared libraries
2023-08-23 22:06:26 +02:00

135 lines
3.7 KiB
Go

package hcl
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/jinzhu/copier"
"github.com/zclconf/go-cty/cty"
"code.icb4dc0.de/buildr/buildr/modules"
)
func (s ModulesSpec) prepareBlockForParsing(
block GenericBlock,
modType modules.Category,
evalCtx *hcl.EvalContext,
outDir string,
) (blockPreparationResult, error) {
syntaxBlock, ok := block.BlockBody.(*hclsyntax.Body)
if !ok {
return blockPreparationResult{
Specs: []blockParsingSpec{
parsingSpecOf(modType, block, nil),
},
ParsingRepresentation: cty.EmptyObjectVal,
}, nil
}
forEachAttr, ok := syntaxBlock.Attributes["for_each"]
if !ok {
initBlock(syntaxBlock, modType, block.BlockName, outDir)
bodyAsValue, _ := s.parseSyntaxBodyToObject(syntaxBlock, evalCtx, true)
return blockPreparationResult{
Specs: []blockParsingSpec{parsingSpecOf(modType, block, nil)},
ParsingRepresentation: cty.ObjectVal(bodyAsValue),
}, nil
}
delete(syntaxBlock.Attributes, "for_each")
val, diags := forEachAttr.Expr.Value(evalCtx)
if diags.HasErrors() {
return blockPreparationResult{}, diags
}
if !val.Type().IsCollectionType() {
return blockPreparationResult{}, fmt.Errorf("cannot for_each over non-collection value")
}
forEachTarget := make(map[string]cty.Value)
elementType := val.Type().ElementType()
switch {
case val.Type().IsSetType():
if !elementType.Equals(cty.String) {
return blockPreparationResult{}, fmt.Errorf("cannot use non-string type as set/list element type: %v", elementType)
}
for _, val := range val.AsValueSet().Values() {
forEachTarget[val.AsString()] = cty.StringVal("")
}
case val.Type().IsListType():
if !elementType.Equals(cty.String) {
return blockPreparationResult{}, fmt.Errorf("cannot use non-string type as set/list element type: %v", elementType)
}
for _, val := range val.AsValueSet().Values() {
forEachTarget[val.AsString()] = cty.StringVal("")
}
case val.Type().IsMapType():
forEachTarget = val.AsValueMap()
}
result := blockPreparationResult{
Specs: make([]blockParsingSpec, 0, len(forEachTarget)),
}
parsingRepresentation := make(map[string]cty.Value, len(forEachTarget))
for k, v := range forEachTarget {
vals := map[string]cty.Value{
"each": cty.ObjectVal(map[string]cty.Value{
"key": cty.StringVal(k),
"value": v,
}),
}
blockCopy := new(hclsyntax.Body)
if err := copier.CopyWithOption(blockCopy, syntaxBlock, copier.Option{DeepCopy: true}); err != nil {
return blockPreparationResult{}, err
}
blockName := fmt.Sprintf("%s_%s", block.BlockName, k)
gb := GenericBlock{
ModuleName: block.ModuleName,
BlockName: blockName,
BlockBody: initBlock(blockCopy, modType, blockName, outDir),
}
result.Specs = append(result.Specs, parsingSpecOf(modType, gb, vals))
bodyAsValue, _ := s.parseSyntaxBodyToObject(blockCopy, evalCtx, true)
parsingRepresentation[k] = cty.ObjectVal(bodyAsValue)
}
result.ParsingRepresentation = cty.MapVal(parsingRepresentation)
return result, nil
}
func parsingSpecOf(modType modules.Category, block GenericBlock, additionalVariables map[string]cty.Value) blockParsingSpec {
if additionalVariables == nil {
additionalVariables = make(map[string]cty.Value)
}
return blockParsingSpec{
Type: modType,
Block: block,
AdditionalParsingVariables: additionalVariables,
}
}
type blockParsingSpec struct {
AdditionalParsingVariables map[string]cty.Value
Block GenericBlock
Type modules.Category
}
type blockPreparationResult struct {
ParsingRepresentation cty.Value
Specs []blockParsingSpec
}