github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/configs/module_call.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/gohcl"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  )
    10  
    11  // ModuleCall represents a "module" block in a module or file.
    12  type ModuleCall struct {
    13  	Name string
    14  
    15  	SourceAddr      string
    16  	SourceAddrRange hcl.Range
    17  	SourceSet       bool
    18  
    19  	Config hcl.Body
    20  
    21  	Version VersionConstraint
    22  
    23  	Count   hcl.Expression
    24  	ForEach hcl.Expression
    25  
    26  	Providers []PassedProviderConfig
    27  
    28  	DependsOn []hcl.Traversal
    29  
    30  	DeclRange hcl.Range
    31  }
    32  
    33  func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagnostics) {
    34  	var diags hcl.Diagnostics
    35  
    36  	mc := &ModuleCall{
    37  		Name:      block.Labels[0],
    38  		DeclRange: block.DefRange,
    39  	}
    40  
    41  	schema := moduleBlockSchema
    42  	if override {
    43  		schema = schemaForOverrides(schema)
    44  	}
    45  
    46  	content, remain, moreDiags := block.Body.PartialContent(schema)
    47  	diags = append(diags, moreDiags...)
    48  	mc.Config = remain
    49  
    50  	if !hclsyntax.ValidIdentifier(mc.Name) {
    51  		diags = append(diags, &hcl.Diagnostic{
    52  			Severity: hcl.DiagError,
    53  			Summary:  "Invalid module instance name",
    54  			Detail:   badIdentifierDetail,
    55  			Subject:  &block.LabelRanges[0],
    56  		})
    57  	}
    58  
    59  	if attr, exists := content.Attributes["source"]; exists {
    60  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &mc.SourceAddr)
    61  		diags = append(diags, valDiags...)
    62  		mc.SourceAddrRange = attr.Expr.Range()
    63  		mc.SourceSet = true
    64  	}
    65  
    66  	if attr, exists := content.Attributes["version"]; exists {
    67  		var versionDiags hcl.Diagnostics
    68  		mc.Version, versionDiags = decodeVersionConstraint(attr)
    69  		diags = append(diags, versionDiags...)
    70  	}
    71  
    72  	if attr, exists := content.Attributes["count"]; exists {
    73  		mc.Count = attr.Expr
    74  	}
    75  
    76  	if attr, exists := content.Attributes["for_each"]; exists {
    77  		if mc.Count != nil {
    78  			diags = append(diags, &hcl.Diagnostic{
    79  				Severity: hcl.DiagError,
    80  				Summary:  `Invalid combination of "count" and "for_each"`,
    81  				Detail:   `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`,
    82  				Subject:  &attr.NameRange,
    83  			})
    84  		}
    85  
    86  		mc.ForEach = attr.Expr
    87  	}
    88  
    89  	if attr, exists := content.Attributes["depends_on"]; exists {
    90  		deps, depsDiags := decodeDependsOn(attr)
    91  		diags = append(diags, depsDiags...)
    92  		mc.DependsOn = append(mc.DependsOn, deps...)
    93  	}
    94  
    95  	if attr, exists := content.Attributes["providers"]; exists {
    96  		seen := make(map[string]hcl.Range)
    97  		pairs, pDiags := hcl.ExprMap(attr.Expr)
    98  		diags = append(diags, pDiags...)
    99  		for _, pair := range pairs {
   100  			key, keyDiags := decodeProviderConfigRef(pair.Key, "providers")
   101  			diags = append(diags, keyDiags...)
   102  			value, valueDiags := decodeProviderConfigRef(pair.Value, "providers")
   103  			diags = append(diags, valueDiags...)
   104  			if keyDiags.HasErrors() || valueDiags.HasErrors() {
   105  				continue
   106  			}
   107  
   108  			matchKey := key.String()
   109  			if prev, exists := seen[matchKey]; exists {
   110  				diags = append(diags, &hcl.Diagnostic{
   111  					Severity: hcl.DiagError,
   112  					Summary:  "Duplicate provider address",
   113  					Detail:   fmt.Sprintf("A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once.", matchKey, prev),
   114  					Subject:  pair.Value.Range().Ptr(),
   115  				})
   116  				continue
   117  			}
   118  
   119  			rng := hcl.RangeBetween(pair.Key.Range(), pair.Value.Range())
   120  			seen[matchKey] = rng
   121  			mc.Providers = append(mc.Providers, PassedProviderConfig{
   122  				InChild:  key,
   123  				InParent: value,
   124  			})
   125  		}
   126  	}
   127  
   128  	var seenEscapeBlock *hcl.Block
   129  	for _, block := range content.Blocks {
   130  		switch block.Type {
   131  		case "_":
   132  			if seenEscapeBlock != nil {
   133  				diags = append(diags, &hcl.Diagnostic{
   134  					Severity: hcl.DiagError,
   135  					Summary:  "Duplicate escaping block",
   136  					Detail: fmt.Sprintf(
   137  						"The special block type \"_\" can be used to force particular arguments to be interpreted as module input variables rather than as meta-arguments, but each module block can have only one such block. The first escaping block was at %s.",
   138  						seenEscapeBlock.DefRange,
   139  					),
   140  					Subject: &block.DefRange,
   141  				})
   142  				continue
   143  			}
   144  			seenEscapeBlock = block
   145  
   146  			// When there's an escaping block its content merges with the
   147  			// existing config we extracted earlier, so later decoding
   148  			// will see a blend of both.
   149  			mc.Config = hcl.MergeBodies([]hcl.Body{mc.Config, block.Body})
   150  
   151  		default:
   152  			// All of the other block types in our schema are reserved.
   153  			diags = append(diags, &hcl.Diagnostic{
   154  				Severity: hcl.DiagError,
   155  				Summary:  "Reserved block type name in module block",
   156  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   157  				Subject:  &block.TypeRange,
   158  			})
   159  		}
   160  	}
   161  
   162  	return mc, diags
   163  }
   164  
   165  // PassedProviderConfig represents a provider config explicitly passed down to
   166  // a child module, possibly giving it a new local address in the process.
   167  type PassedProviderConfig struct {
   168  	InChild  *ProviderConfigRef
   169  	InParent *ProviderConfigRef
   170  }
   171  
   172  var moduleBlockSchema = &hcl.BodySchema{
   173  	Attributes: []hcl.AttributeSchema{
   174  		{
   175  			Name:     "source",
   176  			Required: true,
   177  		},
   178  		{
   179  			Name: "version",
   180  		},
   181  		{
   182  			Name: "count",
   183  		},
   184  		{
   185  			Name: "for_each",
   186  		},
   187  		{
   188  			Name: "depends_on",
   189  		},
   190  		{
   191  			Name: "providers",
   192  		},
   193  	},
   194  	Blocks: []hcl.BlockHeaderSchema{
   195  		{Type: "_"}, // meta-argument escaping block
   196  
   197  		// These are all reserved for future use.
   198  		{Type: "lifecycle"},
   199  		{Type: "locals"},
   200  		{Type: "provider", LabelNames: []string{"type"}},
   201  	},
   202  }