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