github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/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  	"github.com/hashicorp/terraform/internal/addrs"
    10  	"github.com/hashicorp/terraform/internal/getmodules"
    11  )
    12  
    13  // ModuleCall represents a "module" block in a module or file.
    14  type ModuleCall struct {
    15  	Name string
    16  
    17  	SourceAddr      addrs.ModuleSource
    18  	SourceAddrRaw   string
    19  	SourceAddrRange hcl.Range
    20  	SourceSet       bool
    21  
    22  	Config hcl.Body
    23  
    24  	Version VersionConstraint
    25  
    26  	Count   hcl.Expression
    27  	ForEach hcl.Expression
    28  
    29  	Providers []PassedProviderConfig
    30  
    31  	DependsOn []hcl.Traversal
    32  
    33  	DeclRange hcl.Range
    34  }
    35  
    36  func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagnostics) {
    37  	var diags hcl.Diagnostics
    38  
    39  	mc := &ModuleCall{
    40  		Name:      block.Labels[0],
    41  		DeclRange: block.DefRange,
    42  	}
    43  
    44  	schema := moduleBlockSchema
    45  	if override {
    46  		schema = schemaForOverrides(schema)
    47  	}
    48  
    49  	content, remain, moreDiags := block.Body.PartialContent(schema)
    50  	diags = append(diags, moreDiags...)
    51  	mc.Config = remain
    52  
    53  	if !hclsyntax.ValidIdentifier(mc.Name) {
    54  		diags = append(diags, &hcl.Diagnostic{
    55  			Severity: hcl.DiagError,
    56  			Summary:  "Invalid module instance name",
    57  			Detail:   badIdentifierDetail,
    58  			Subject:  &block.LabelRanges[0],
    59  		})
    60  	}
    61  
    62  	haveVersionArg := false
    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  		haveVersionArg = true
    68  	}
    69  
    70  	if attr, exists := content.Attributes["source"]; exists {
    71  		mc.SourceSet = true
    72  		mc.SourceAddrRange = attr.Expr.Range()
    73  		valDiags := gohcl.DecodeExpression(attr.Expr, nil, &mc.SourceAddrRaw)
    74  		diags = append(diags, valDiags...)
    75  		if !valDiags.HasErrors() {
    76  			var addr addrs.ModuleSource
    77  			var err error
    78  			if haveVersionArg {
    79  				addr, err = addrs.ParseModuleSourceRegistry(mc.SourceAddrRaw)
    80  			} else {
    81  				addr, err = addrs.ParseModuleSource(mc.SourceAddrRaw)
    82  			}
    83  			mc.SourceAddr = addr
    84  			if err != nil {
    85  				// NOTE: We leave mc.SourceAddr as nil for any situation where the
    86  				// source attribute is invalid, so any code which tries to carefully
    87  				// use the partial result of a failed config decode must be
    88  				// resilient to that.
    89  				mc.SourceAddr = nil
    90  
    91  				// NOTE: In practice it's actually very unlikely to end up here,
    92  				// because our source address parser can turn just about any string
    93  				// into some sort of remote package address, and so for most errors
    94  				// we'll detect them only during module installation. There are
    95  				// still a _few_ purely-syntax errors we can catch at parsing time,
    96  				// though, mostly related to remote package sub-paths and local
    97  				// paths.
    98  				switch err := err.(type) {
    99  				case *getmodules.MaybeRelativePathErr:
   100  					diags = append(diags, &hcl.Diagnostic{
   101  						Severity: hcl.DiagError,
   102  						Summary:  "Invalid module source address",
   103  						Detail: fmt.Sprintf(
   104  							"Terraform failed to determine your intended installation method for remote module package %q.\n\nIf you intended this as a path relative to the current module, use \"./%s\" instead. The \"./\" prefix indicates that the address is a relative filesystem path.",
   105  							err.Addr, err.Addr,
   106  						),
   107  						Subject: mc.SourceAddrRange.Ptr(),
   108  					})
   109  				default:
   110  					if haveVersionArg {
   111  						// In this case we'll include some extra context that
   112  						// we assumed a registry source address due to the
   113  						// version argument.
   114  						diags = append(diags, &hcl.Diagnostic{
   115  							Severity: hcl.DiagError,
   116  							Summary:  "Invalid registry module source address",
   117  							Detail:   fmt.Sprintf("Failed to parse module registry address: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", err),
   118  							Subject:  mc.SourceAddrRange.Ptr(),
   119  						})
   120  					} else {
   121  						diags = append(diags, &hcl.Diagnostic{
   122  							Severity: hcl.DiagError,
   123  							Summary:  "Invalid module source address",
   124  							Detail:   fmt.Sprintf("Failed to parse module source address: %s.", err),
   125  							Subject:  mc.SourceAddrRange.Ptr(),
   126  						})
   127  					}
   128  				}
   129  			}
   130  		}
   131  	}
   132  
   133  	if attr, exists := content.Attributes["count"]; exists {
   134  		mc.Count = attr.Expr
   135  	}
   136  
   137  	if attr, exists := content.Attributes["for_each"]; exists {
   138  		if mc.Count != nil {
   139  			diags = append(diags, &hcl.Diagnostic{
   140  				Severity: hcl.DiagError,
   141  				Summary:  `Invalid combination of "count" and "for_each"`,
   142  				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.`,
   143  				Subject:  &attr.NameRange,
   144  			})
   145  		}
   146  
   147  		mc.ForEach = attr.Expr
   148  	}
   149  
   150  	if attr, exists := content.Attributes["depends_on"]; exists {
   151  		deps, depsDiags := decodeDependsOn(attr)
   152  		diags = append(diags, depsDiags...)
   153  		mc.DependsOn = append(mc.DependsOn, deps...)
   154  	}
   155  
   156  	if attr, exists := content.Attributes["providers"]; exists {
   157  		seen := make(map[string]hcl.Range)
   158  		pairs, pDiags := hcl.ExprMap(attr.Expr)
   159  		diags = append(diags, pDiags...)
   160  		for _, pair := range pairs {
   161  			key, keyDiags := decodeProviderConfigRef(pair.Key, "providers")
   162  			diags = append(diags, keyDiags...)
   163  			value, valueDiags := decodeProviderConfigRef(pair.Value, "providers")
   164  			diags = append(diags, valueDiags...)
   165  			if keyDiags.HasErrors() || valueDiags.HasErrors() {
   166  				continue
   167  			}
   168  
   169  			matchKey := key.String()
   170  			if prev, exists := seen[matchKey]; exists {
   171  				diags = append(diags, &hcl.Diagnostic{
   172  					Severity: hcl.DiagError,
   173  					Summary:  "Duplicate provider address",
   174  					Detail:   fmt.Sprintf("A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once.", matchKey, prev),
   175  					Subject:  pair.Value.Range().Ptr(),
   176  				})
   177  				continue
   178  			}
   179  
   180  			rng := hcl.RangeBetween(pair.Key.Range(), pair.Value.Range())
   181  			seen[matchKey] = rng
   182  			mc.Providers = append(mc.Providers, PassedProviderConfig{
   183  				InChild:  key,
   184  				InParent: value,
   185  			})
   186  		}
   187  	}
   188  
   189  	var seenEscapeBlock *hcl.Block
   190  	for _, block := range content.Blocks {
   191  		switch block.Type {
   192  		case "_":
   193  			if seenEscapeBlock != nil {
   194  				diags = append(diags, &hcl.Diagnostic{
   195  					Severity: hcl.DiagError,
   196  					Summary:  "Duplicate escaping block",
   197  					Detail: fmt.Sprintf(
   198  						"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.",
   199  						seenEscapeBlock.DefRange,
   200  					),
   201  					Subject: &block.DefRange,
   202  				})
   203  				continue
   204  			}
   205  			seenEscapeBlock = block
   206  
   207  			// When there's an escaping block its content merges with the
   208  			// existing config we extracted earlier, so later decoding
   209  			// will see a blend of both.
   210  			mc.Config = hcl.MergeBodies([]hcl.Body{mc.Config, block.Body})
   211  
   212  		default:
   213  			// All of the other block types in our schema are reserved.
   214  			diags = append(diags, &hcl.Diagnostic{
   215  				Severity: hcl.DiagError,
   216  				Summary:  "Reserved block type name in module block",
   217  				Detail:   fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
   218  				Subject:  &block.TypeRange,
   219  			})
   220  		}
   221  	}
   222  
   223  	return mc, diags
   224  }
   225  
   226  // EntersNewPackage returns true if this call is to an external module, either
   227  // directly via a remote source address or indirectly via a registry source
   228  // address.
   229  //
   230  // Other behaviors in Terraform may treat package crossings as a special
   231  // situation, because that indicates that the caller and callee can change
   232  // independently of one another and thus we should disallow using any features
   233  // where the caller assumes anything about the callee other than its input
   234  // variables, required provider configurations, and output values.
   235  func (mc *ModuleCall) EntersNewPackage() bool {
   236  	return moduleSourceAddrEntersNewPackage(mc.SourceAddr)
   237  }
   238  
   239  // PassedProviderConfig represents a provider config explicitly passed down to
   240  // a child module, possibly giving it a new local address in the process.
   241  type PassedProviderConfig struct {
   242  	InChild  *ProviderConfigRef
   243  	InParent *ProviderConfigRef
   244  }
   245  
   246  var moduleBlockSchema = &hcl.BodySchema{
   247  	Attributes: []hcl.AttributeSchema{
   248  		{
   249  			Name:     "source",
   250  			Required: true,
   251  		},
   252  		{
   253  			Name: "version",
   254  		},
   255  		{
   256  			Name: "count",
   257  		},
   258  		{
   259  			Name: "for_each",
   260  		},
   261  		{
   262  			Name: "depends_on",
   263  		},
   264  		{
   265  			Name: "providers",
   266  		},
   267  	},
   268  	Blocks: []hcl.BlockHeaderSchema{
   269  		{Type: "_"}, // meta-argument escaping block
   270  
   271  		// These are all reserved for future use.
   272  		{Type: "lifecycle"},
   273  		{Type: "locals"},
   274  		{Type: "provider", LabelNames: []string{"type"}},
   275  	},
   276  }
   277  
   278  func moduleSourceAddrEntersNewPackage(addr addrs.ModuleSource) bool {
   279  	switch addr.(type) {
   280  	case nil:
   281  		// There are only two situations where we should get here:
   282  		// - We've been asked about the source address of the root module,
   283  		//   which is always nil.
   284  		// - We've been asked about a ModuleCall that is part of the partial
   285  		//   result of a failed decode.
   286  		// The root module exists outside of all module packages, so we'll
   287  		// just return false for that case. For the error case it doesn't
   288  		// really matter what we return as long as we don't panic, because
   289  		// we only make a best-effort to allow careful inspection of objects
   290  		// representing invalid configuration.
   291  		return false
   292  	case addrs.ModuleSourceLocal:
   293  		// Local source addresses are the only address type that remains within
   294  		// the same package.
   295  		return false
   296  	default:
   297  		// All other address types enter a new package.
   298  		return true
   299  	}
   300  }