github.com/hashicorp/terraform-plugin-sdk@v1.17.2/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  	mc := &ModuleCall{
    35  		Name:      block.Labels[0],
    36  		DeclRange: block.DefRange,
    37  	}
    38  
    39  	schema := moduleBlockSchema
    40  	if override {
    41  		schema = schemaForOverrides(schema)
    42  	}
    43  
    44  	content, remain, diags := block.Body.PartialContent(schema)
    45  	mc.Config = remain
    46  
    47  	if !hclsyntax.ValidIdentifier(mc.Name) {
    48  		diags = append(diags, &hcl.Diagnostic{
    49  			Severity: hcl.DiagError,
    50  			Summary:  "Invalid module instance name",
    51  			Detail:   badIdentifierDetail,
    52  			Subject:  &block.LabelRanges[0],
    53  		})
    54  	}
    55  
    56  	if attr, exists := content.Attributes["source"]; exists {
    57  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &mc.SourceAddr)
    58  		diags = append(diags, valDiags...)
    59  		mc.SourceAddrRange = attr.Expr.Range()
    60  		mc.SourceSet = true
    61  	}
    62  
    63  	if attr, exists := content.Attributes["version"]; exists {
    64  		var versionDiags hcl.Diagnostics
    65  		mc.Version, versionDiags = decodeVersionConstraint(attr)
    66  		diags = append(diags, versionDiags...)
    67  	}
    68  
    69  	if attr, exists := content.Attributes["count"]; exists {
    70  		mc.Count = attr.Expr
    71  
    72  		// We currently parse this, but don't yet do anything with it.
    73  		diags = append(diags, &hcl.Diagnostic{
    74  			Severity: hcl.DiagError,
    75  			Summary:  "Reserved argument name in module block",
    76  			Detail:   fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
    77  			Subject:  &attr.NameRange,
    78  		})
    79  	}
    80  
    81  	if attr, exists := content.Attributes["for_each"]; exists {
    82  		mc.ForEach = attr.Expr
    83  
    84  		// We currently parse this, but don't yet do anything with it.
    85  		diags = append(diags, &hcl.Diagnostic{
    86  			Severity: hcl.DiagError,
    87  			Summary:  "Reserved argument name in module block",
    88  			Detail:   fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
    89  			Subject:  &attr.NameRange,
    90  		})
    91  	}
    92  
    93  	if attr, exists := content.Attributes["depends_on"]; exists {
    94  		deps, depsDiags := decodeDependsOn(attr)
    95  		diags = append(diags, depsDiags...)
    96  		mc.DependsOn = append(mc.DependsOn, deps...)
    97  
    98  		// We currently parse this, but don't yet do anything with it.
    99  		diags = append(diags, &hcl.Diagnostic{
   100  			Severity: hcl.DiagError,
   101  			Summary:  "Reserved argument name in module block",
   102  			Detail:   fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
   103  			Subject:  &attr.NameRange,
   104  		})
   105  	}
   106  
   107  	if attr, exists := content.Attributes["providers"]; exists {
   108  		seen := make(map[string]hcl.Range)
   109  		pairs, pDiags := hcl.ExprMap(attr.Expr)
   110  		diags = append(diags, pDiags...)
   111  		for _, pair := range pairs {
   112  			key, keyDiags := decodeProviderConfigRef(pair.Key, "providers")
   113  			diags = append(diags, keyDiags...)
   114  			value, valueDiags := decodeProviderConfigRef(pair.Value, "providers")
   115  			diags = append(diags, valueDiags...)
   116  			if keyDiags.HasErrors() || valueDiags.HasErrors() {
   117  				continue
   118  			}
   119  
   120  			matchKey := key.String()
   121  			if prev, exists := seen[matchKey]; exists {
   122  				diags = append(diags, &hcl.Diagnostic{
   123  					Severity: hcl.DiagError,
   124  					Summary:  "Duplicate provider address",
   125  					Detail:   fmt.Sprintf("A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once.", matchKey, prev),
   126  					Subject:  pair.Value.Range().Ptr(),
   127  				})
   128  				continue
   129  			}
   130  
   131  			rng := hcl.RangeBetween(pair.Key.Range(), pair.Value.Range())
   132  			seen[matchKey] = rng
   133  			mc.Providers = append(mc.Providers, PassedProviderConfig{
   134  				InChild:  key,
   135  				InParent: value,
   136  			})
   137  		}
   138  	}
   139  
   140  	// Reserved block types (all of them)
   141  	for _, block := range content.Blocks {
   142  		diags = append(diags, &hcl.Diagnostic{
   143  			Severity: hcl.DiagError,
   144  			Summary:  "Reserved block type name in module block",
   145  			Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   146  			Subject:  &block.TypeRange,
   147  		})
   148  	}
   149  
   150  	return mc, diags
   151  }
   152  
   153  // PassedProviderConfig represents a provider config explicitly passed down to
   154  // a child module, possibly giving it a new local address in the process.
   155  type PassedProviderConfig struct {
   156  	InChild  *ProviderConfigRef
   157  	InParent *ProviderConfigRef
   158  }
   159  
   160  var moduleBlockSchema = &hcl.BodySchema{
   161  	Attributes: []hcl.AttributeSchema{
   162  		{
   163  			Name:     "source",
   164  			Required: true,
   165  		},
   166  		{
   167  			Name: "version",
   168  		},
   169  		{
   170  			Name: "count",
   171  		},
   172  		{
   173  			Name: "for_each",
   174  		},
   175  		{
   176  			Name: "depends_on",
   177  		},
   178  		{
   179  			Name: "providers",
   180  		},
   181  	},
   182  	Blocks: []hcl.BlockHeaderSchema{
   183  		// These are all reserved for future use.
   184  		{Type: "lifecycle"},
   185  		{Type: "locals"},
   186  		{Type: "provider", LabelNames: []string{"type"}},
   187  	},
   188  }