github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/configs/module.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/experiments"
    10  )
    11  
    12  // Module is a container for a set of configuration constructs that are
    13  // evaluated within a common namespace.
    14  type Module struct {
    15  	// SourceDir is the filesystem directory that the module was loaded from.
    16  	//
    17  	// This is populated automatically only for configurations loaded with
    18  	// LoadConfigDir. If the parser is using a virtual filesystem then the
    19  	// path here will be in terms of that virtual filesystem.
    20  
    21  	// Any other caller that constructs a module directly with NewModule may
    22  	// assign a suitable value to this attribute before using it for other
    23  	// purposes. It should be treated as immutable by all consumers of Module
    24  	// values.
    25  	SourceDir string
    26  
    27  	CoreVersionConstraints []VersionConstraint
    28  
    29  	ActiveExperiments experiments.Set
    30  
    31  	Backend              *Backend
    32  	ProviderConfigs      map[string]*Provider
    33  	ProviderRequirements *RequiredProviders
    34  	ProviderLocalNames   map[addrs.Provider]string
    35  	ProviderMetas        map[addrs.Provider]*ProviderMeta
    36  
    37  	Variables map[string]*Variable
    38  	Locals    map[string]*Local
    39  	Outputs   map[string]*Output
    40  
    41  	ModuleCalls map[string]*ModuleCall
    42  
    43  	ManagedResources map[string]*Resource
    44  	DataResources    map[string]*Resource
    45  
    46  	Moved []*Moved
    47  }
    48  
    49  // File describes the contents of a single configuration file.
    50  //
    51  // Individual files are not usually used alone, but rather combined together
    52  // with other files (conventionally, those in the same directory) to produce
    53  // a *Module, using NewModule.
    54  //
    55  // At the level of an individual file we represent directly the structural
    56  // elements present in the file, without any attempt to detect conflicting
    57  // declarations. A File object can therefore be used for some basic static
    58  // analysis of individual elements, but must be built into a Module to detect
    59  // duplicate declarations.
    60  type File struct {
    61  	CoreVersionConstraints []VersionConstraint
    62  
    63  	ActiveExperiments experiments.Set
    64  
    65  	Backends          []*Backend
    66  	ProviderConfigs   []*Provider
    67  	ProviderMetas     []*ProviderMeta
    68  	RequiredProviders []*RequiredProviders
    69  
    70  	Variables []*Variable
    71  	Locals    []*Local
    72  	Outputs   []*Output
    73  
    74  	ModuleCalls []*ModuleCall
    75  
    76  	ManagedResources []*Resource
    77  	DataResources    []*Resource
    78  
    79  	Moved []*Moved
    80  }
    81  
    82  // NewModule takes a list of primary files and a list of override files and
    83  // produces a *Module by combining the files together.
    84  //
    85  // If there are any conflicting declarations in the given files -- for example,
    86  // if the same variable name is defined twice -- then the resulting module
    87  // will be incomplete and error diagnostics will be returned. Careful static
    88  // analysis of the returned Module is still possible in this case, but the
    89  // module will probably not be semantically valid.
    90  func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
    91  	var diags hcl.Diagnostics
    92  	mod := &Module{
    93  		ProviderConfigs:    map[string]*Provider{},
    94  		ProviderLocalNames: map[addrs.Provider]string{},
    95  		Variables:          map[string]*Variable{},
    96  		Locals:             map[string]*Local{},
    97  		Outputs:            map[string]*Output{},
    98  		ModuleCalls:        map[string]*ModuleCall{},
    99  		ManagedResources:   map[string]*Resource{},
   100  		DataResources:      map[string]*Resource{},
   101  		ProviderMetas:      map[addrs.Provider]*ProviderMeta{},
   102  	}
   103  
   104  	// Process the required_providers blocks first, to ensure that all
   105  	// resources have access to the correct provider FQNs
   106  	for _, file := range primaryFiles {
   107  		for _, r := range file.RequiredProviders {
   108  			if mod.ProviderRequirements != nil {
   109  				diags = append(diags, &hcl.Diagnostic{
   110  					Severity: hcl.DiagError,
   111  					Summary:  "Duplicate required providers configuration",
   112  					Detail:   fmt.Sprintf("A module may have only one required providers configuration. The required providers were previously configured at %s.", mod.ProviderRequirements.DeclRange),
   113  					Subject:  &r.DeclRange,
   114  				})
   115  				continue
   116  			}
   117  			mod.ProviderRequirements = r
   118  		}
   119  	}
   120  
   121  	// If no required_providers block is configured, create a useful empty
   122  	// state to reduce nil checks elsewhere
   123  	if mod.ProviderRequirements == nil {
   124  		mod.ProviderRequirements = &RequiredProviders{
   125  			RequiredProviders: make(map[string]*RequiredProvider),
   126  		}
   127  	}
   128  
   129  	// Any required_providers blocks in override files replace the entire
   130  	// block for each provider
   131  	for _, file := range overrideFiles {
   132  		for _, override := range file.RequiredProviders {
   133  			for name, rp := range override.RequiredProviders {
   134  				mod.ProviderRequirements.RequiredProviders[name] = rp
   135  			}
   136  		}
   137  	}
   138  
   139  	for _, file := range primaryFiles {
   140  		fileDiags := mod.appendFile(file)
   141  		diags = append(diags, fileDiags...)
   142  	}
   143  
   144  	for _, file := range overrideFiles {
   145  		fileDiags := mod.mergeFile(file)
   146  		diags = append(diags, fileDiags...)
   147  	}
   148  
   149  	diags = append(diags, checkModuleExperiments(mod)...)
   150  
   151  	// Generate the FQN -> LocalProviderName map
   152  	mod.gatherProviderLocalNames()
   153  
   154  	return mod, diags
   155  }
   156  
   157  // ResourceByAddr returns the configuration for the resource with the given
   158  // address, or nil if there is no such resource.
   159  func (m *Module) ResourceByAddr(addr addrs.Resource) *Resource {
   160  	key := addr.String()
   161  	switch addr.Mode {
   162  	case addrs.ManagedResourceMode:
   163  		return m.ManagedResources[key]
   164  	case addrs.DataResourceMode:
   165  		return m.DataResources[key]
   166  	default:
   167  		return nil
   168  	}
   169  }
   170  
   171  func (m *Module) appendFile(file *File) hcl.Diagnostics {
   172  	var diags hcl.Diagnostics
   173  
   174  	// If there are any conflicting requirements then we'll catch them
   175  	// when we actually check these constraints.
   176  	m.CoreVersionConstraints = append(m.CoreVersionConstraints, file.CoreVersionConstraints...)
   177  
   178  	m.ActiveExperiments = experiments.SetUnion(m.ActiveExperiments, file.ActiveExperiments)
   179  
   180  	for _, b := range file.Backends {
   181  		if m.Backend != nil {
   182  			diags = append(diags, &hcl.Diagnostic{
   183  				Severity: hcl.DiagError,
   184  				Summary:  "Duplicate backend configuration",
   185  				Detail:   fmt.Sprintf("A module may have only one backend configuration. The backend was previously configured at %s.", m.Backend.DeclRange),
   186  				Subject:  &b.DeclRange,
   187  			})
   188  			continue
   189  		}
   190  		m.Backend = b
   191  	}
   192  
   193  	for _, pc := range file.ProviderConfigs {
   194  		key := pc.moduleUniqueKey()
   195  		if existing, exists := m.ProviderConfigs[key]; exists {
   196  			if existing.Alias == "" {
   197  				diags = append(diags, &hcl.Diagnostic{
   198  					Severity: hcl.DiagError,
   199  					Summary:  "Duplicate provider configuration",
   200  					Detail:   fmt.Sprintf("A default (non-aliased) provider configuration for %q was already given at %s. If multiple configurations are required, set the \"alias\" argument for alternative configurations.", existing.Name, existing.DeclRange),
   201  					Subject:  &pc.DeclRange,
   202  				})
   203  			} else {
   204  				diags = append(diags, &hcl.Diagnostic{
   205  					Severity: hcl.DiagError,
   206  					Summary:  "Duplicate provider configuration",
   207  					Detail:   fmt.Sprintf("A provider configuration for %q with alias %q was already given at %s. Each configuration for the same provider must have a distinct alias.", existing.Name, existing.Alias, existing.DeclRange),
   208  					Subject:  &pc.DeclRange,
   209  				})
   210  			}
   211  			continue
   212  		}
   213  		m.ProviderConfigs[key] = pc
   214  	}
   215  
   216  	for _, pm := range file.ProviderMetas {
   217  		provider := m.ProviderForLocalConfig(addrs.LocalProviderConfig{LocalName: pm.Provider})
   218  		if existing, exists := m.ProviderMetas[provider]; exists {
   219  			diags = append(diags, &hcl.Diagnostic{
   220  				Severity: hcl.DiagError,
   221  				Summary:  "Duplicate provider_meta block",
   222  				Detail:   fmt.Sprintf("A provider_meta block for provider %q was already declared at %s. Providers may only have one provider_meta block per module.", existing.Provider, existing.DeclRange),
   223  				Subject:  &pm.DeclRange,
   224  			})
   225  		}
   226  		m.ProviderMetas[provider] = pm
   227  	}
   228  
   229  	for _, v := range file.Variables {
   230  		if existing, exists := m.Variables[v.Name]; exists {
   231  			diags = append(diags, &hcl.Diagnostic{
   232  				Severity: hcl.DiagError,
   233  				Summary:  "Duplicate variable declaration",
   234  				Detail:   fmt.Sprintf("A variable named %q was already declared at %s. Variable names must be unique within a module.", existing.Name, existing.DeclRange),
   235  				Subject:  &v.DeclRange,
   236  			})
   237  		}
   238  		m.Variables[v.Name] = v
   239  	}
   240  
   241  	for _, l := range file.Locals {
   242  		if existing, exists := m.Locals[l.Name]; exists {
   243  			diags = append(diags, &hcl.Diagnostic{
   244  				Severity: hcl.DiagError,
   245  				Summary:  "Duplicate local value definition",
   246  				Detail:   fmt.Sprintf("A local value named %q was already defined at %s. Local value names must be unique within a module.", existing.Name, existing.DeclRange),
   247  				Subject:  &l.DeclRange,
   248  			})
   249  		}
   250  		m.Locals[l.Name] = l
   251  	}
   252  
   253  	for _, o := range file.Outputs {
   254  		if existing, exists := m.Outputs[o.Name]; exists {
   255  			diags = append(diags, &hcl.Diagnostic{
   256  				Severity: hcl.DiagError,
   257  				Summary:  "Duplicate output definition",
   258  				Detail:   fmt.Sprintf("An output named %q was already defined at %s. Output names must be unique within a module.", existing.Name, existing.DeclRange),
   259  				Subject:  &o.DeclRange,
   260  			})
   261  		}
   262  		m.Outputs[o.Name] = o
   263  	}
   264  
   265  	for _, mc := range file.ModuleCalls {
   266  		if existing, exists := m.ModuleCalls[mc.Name]; exists {
   267  			diags = append(diags, &hcl.Diagnostic{
   268  				Severity: hcl.DiagError,
   269  				Summary:  "Duplicate module call",
   270  				Detail:   fmt.Sprintf("A module call named %q was already defined at %s. Module calls must have unique names within a module.", existing.Name, existing.DeclRange),
   271  				Subject:  &mc.DeclRange,
   272  			})
   273  		}
   274  		m.ModuleCalls[mc.Name] = mc
   275  	}
   276  
   277  	for _, r := range file.ManagedResources {
   278  		key := r.moduleUniqueKey()
   279  		if existing, exists := m.ManagedResources[key]; exists {
   280  			diags = append(diags, &hcl.Diagnostic{
   281  				Severity: hcl.DiagError,
   282  				Summary:  fmt.Sprintf("Duplicate resource %q configuration", existing.Type),
   283  				Detail:   fmt.Sprintf("A %s resource named %q was already declared at %s. Resource names must be unique per type in each module.", existing.Type, existing.Name, existing.DeclRange),
   284  				Subject:  &r.DeclRange,
   285  			})
   286  			continue
   287  		}
   288  		m.ManagedResources[key] = r
   289  
   290  		// set the provider FQN for the resource
   291  		if r.ProviderConfigRef != nil {
   292  			r.Provider = m.ProviderForLocalConfig(r.ProviderConfigAddr())
   293  		} else {
   294  			// an invalid resource name (for e.g. "null resource" instead of
   295  			// "null_resource") can cause a panic down the line in addrs:
   296  			// https://github.com/hashicorp/terraform/issues/25560
   297  			implied, err := addrs.ParseProviderPart(r.Addr().ImpliedProvider())
   298  			if err == nil {
   299  				r.Provider = m.ImpliedProviderForUnqualifiedType(implied)
   300  			}
   301  			// We don't return a diagnostic because the invalid resource name
   302  			// will already have been caught.
   303  		}
   304  	}
   305  
   306  	for _, r := range file.DataResources {
   307  		key := r.moduleUniqueKey()
   308  		if existing, exists := m.DataResources[key]; exists {
   309  			diags = append(diags, &hcl.Diagnostic{
   310  				Severity: hcl.DiagError,
   311  				Summary:  fmt.Sprintf("Duplicate data %q configuration", existing.Type),
   312  				Detail:   fmt.Sprintf("A %s data resource named %q was already declared at %s. Resource names must be unique per type in each module.", existing.Type, existing.Name, existing.DeclRange),
   313  				Subject:  &r.DeclRange,
   314  			})
   315  			continue
   316  		}
   317  		m.DataResources[key] = r
   318  
   319  		// set the provider FQN for the resource
   320  		if r.ProviderConfigRef != nil {
   321  			r.Provider = m.ProviderForLocalConfig(r.ProviderConfigAddr())
   322  		} else {
   323  			// an invalid data source name (for e.g. "null resource" instead of
   324  			// "null_resource") can cause a panic down the line in addrs:
   325  			// https://github.com/hashicorp/terraform/issues/25560
   326  			implied, err := addrs.ParseProviderPart(r.Addr().ImpliedProvider())
   327  			if err == nil {
   328  				r.Provider = m.ImpliedProviderForUnqualifiedType(implied)
   329  			}
   330  			// We don't return a diagnostic because the invalid resource name
   331  			// will already have been caught.
   332  		}
   333  	}
   334  
   335  	// "Moved" blocks just append, because they are all independent
   336  	// of one another at this level. (We handle any references between
   337  	// them at runtime.)
   338  	m.Moved = append(m.Moved, file.Moved...)
   339  
   340  	return diags
   341  }
   342  
   343  func (m *Module) mergeFile(file *File) hcl.Diagnostics {
   344  	var diags hcl.Diagnostics
   345  
   346  	if len(file.CoreVersionConstraints) != 0 {
   347  		// This is a bit of a strange case for overriding since we normally
   348  		// would union together across multiple files anyway, but we'll
   349  		// allow it and have each override file clobber any existing list.
   350  		m.CoreVersionConstraints = nil
   351  		m.CoreVersionConstraints = append(m.CoreVersionConstraints, file.CoreVersionConstraints...)
   352  	}
   353  
   354  	if len(file.Backends) != 0 {
   355  		switch len(file.Backends) {
   356  		case 1:
   357  			m.Backend = file.Backends[0]
   358  		default:
   359  			// An override file with multiple backends is still invalid, even
   360  			// though it can override backends from _other_ files.
   361  			diags = append(diags, &hcl.Diagnostic{
   362  				Severity: hcl.DiagError,
   363  				Summary:  "Duplicate backend configuration",
   364  				Detail:   fmt.Sprintf("Each override file may have only one backend configuration. A backend was previously configured at %s.", file.Backends[0].DeclRange),
   365  				Subject:  &file.Backends[1].DeclRange,
   366  			})
   367  		}
   368  	}
   369  
   370  	for _, pc := range file.ProviderConfigs {
   371  		key := pc.moduleUniqueKey()
   372  		existing, exists := m.ProviderConfigs[key]
   373  		if pc.Alias == "" {
   374  			// We allow overriding a non-existing _default_ provider configuration
   375  			// because the user model is that an absent provider configuration
   376  			// implies an empty provider configuration, which is what the user
   377  			// is therefore overriding here.
   378  			if exists {
   379  				mergeDiags := existing.merge(pc)
   380  				diags = append(diags, mergeDiags...)
   381  			} else {
   382  				m.ProviderConfigs[key] = pc
   383  			}
   384  		} else {
   385  			// For aliased providers, there must be a base configuration to
   386  			// override. This allows us to detect and report alias typos
   387  			// that might otherwise cause the override to not apply.
   388  			if !exists {
   389  				diags = append(diags, &hcl.Diagnostic{
   390  					Severity: hcl.DiagError,
   391  					Summary:  "Missing base provider configuration for override",
   392  					Detail:   fmt.Sprintf("There is no %s provider configuration with the alias %q. An override file can only override an aliased provider configuration that was already defined in a primary configuration file.", pc.Name, pc.Alias),
   393  					Subject:  &pc.DeclRange,
   394  				})
   395  				continue
   396  			}
   397  			mergeDiags := existing.merge(pc)
   398  			diags = append(diags, mergeDiags...)
   399  		}
   400  	}
   401  
   402  	for _, v := range file.Variables {
   403  		existing, exists := m.Variables[v.Name]
   404  		if !exists {
   405  			diags = append(diags, &hcl.Diagnostic{
   406  				Severity: hcl.DiagError,
   407  				Summary:  "Missing base variable declaration to override",
   408  				Detail:   fmt.Sprintf("There is no variable named %q. An override file can only override a variable that was already declared in a primary configuration file.", v.Name),
   409  				Subject:  &v.DeclRange,
   410  			})
   411  			continue
   412  		}
   413  		mergeDiags := existing.merge(v)
   414  		diags = append(diags, mergeDiags...)
   415  	}
   416  
   417  	for _, l := range file.Locals {
   418  		existing, exists := m.Locals[l.Name]
   419  		if !exists {
   420  			diags = append(diags, &hcl.Diagnostic{
   421  				Severity: hcl.DiagError,
   422  				Summary:  "Missing base local value definition to override",
   423  				Detail:   fmt.Sprintf("There is no local value named %q. An override file can only override a local value that was already defined in a primary configuration file.", l.Name),
   424  				Subject:  &l.DeclRange,
   425  			})
   426  			continue
   427  		}
   428  		mergeDiags := existing.merge(l)
   429  		diags = append(diags, mergeDiags...)
   430  	}
   431  
   432  	for _, o := range file.Outputs {
   433  		existing, exists := m.Outputs[o.Name]
   434  		if !exists {
   435  			diags = append(diags, &hcl.Diagnostic{
   436  				Severity: hcl.DiagError,
   437  				Summary:  "Missing base output definition to override",
   438  				Detail:   fmt.Sprintf("There is no output named %q. An override file can only override an output that was already defined in a primary configuration file.", o.Name),
   439  				Subject:  &o.DeclRange,
   440  			})
   441  			continue
   442  		}
   443  		mergeDiags := existing.merge(o)
   444  		diags = append(diags, mergeDiags...)
   445  	}
   446  
   447  	for _, mc := range file.ModuleCalls {
   448  		existing, exists := m.ModuleCalls[mc.Name]
   449  		if !exists {
   450  			diags = append(diags, &hcl.Diagnostic{
   451  				Severity: hcl.DiagError,
   452  				Summary:  "Missing module call to override",
   453  				Detail:   fmt.Sprintf("There is no module call named %q. An override file can only override a module call that was defined in a primary configuration file.", mc.Name),
   454  				Subject:  &mc.DeclRange,
   455  			})
   456  			continue
   457  		}
   458  		mergeDiags := existing.merge(mc)
   459  		diags = append(diags, mergeDiags...)
   460  	}
   461  
   462  	for _, r := range file.ManagedResources {
   463  		key := r.moduleUniqueKey()
   464  		existing, exists := m.ManagedResources[key]
   465  		if !exists {
   466  			diags = append(diags, &hcl.Diagnostic{
   467  				Severity: hcl.DiagError,
   468  				Summary:  "Missing resource to override",
   469  				Detail:   fmt.Sprintf("There is no %s resource named %q. An override file can only override a resource block defined in a primary configuration file.", r.Type, r.Name),
   470  				Subject:  &r.DeclRange,
   471  			})
   472  			continue
   473  		}
   474  		mergeDiags := existing.merge(r, m.ProviderRequirements.RequiredProviders)
   475  		diags = append(diags, mergeDiags...)
   476  	}
   477  
   478  	for _, r := range file.DataResources {
   479  		key := r.moduleUniqueKey()
   480  		existing, exists := m.DataResources[key]
   481  		if !exists {
   482  			diags = append(diags, &hcl.Diagnostic{
   483  				Severity: hcl.DiagError,
   484  				Summary:  "Missing data resource to override",
   485  				Detail:   fmt.Sprintf("There is no %s data resource named %q. An override file can only override a data block defined in a primary configuration file.", r.Type, r.Name),
   486  				Subject:  &r.DeclRange,
   487  			})
   488  			continue
   489  		}
   490  		mergeDiags := existing.merge(r, m.ProviderRequirements.RequiredProviders)
   491  		diags = append(diags, mergeDiags...)
   492  	}
   493  
   494  	for _, m := range file.Moved {
   495  		diags = append(diags, &hcl.Diagnostic{
   496  			Severity: hcl.DiagError,
   497  			Summary:  "Cannot override 'moved' blocks",
   498  			Detail:   "Records of moved objects can appear only in normal files, not in override files.",
   499  			Subject:  m.DeclRange.Ptr(),
   500  		})
   501  	}
   502  
   503  	return diags
   504  }
   505  
   506  // gatherProviderLocalNames is a helper function that populatesA a map of
   507  // provider FQNs -> provider local names. This information is useful for
   508  // user-facing output, which should include both the FQN and LocalName. It must
   509  // only be populated after the module has been parsed.
   510  func (m *Module) gatherProviderLocalNames() {
   511  	providers := make(map[addrs.Provider]string)
   512  	for k, v := range m.ProviderRequirements.RequiredProviders {
   513  		providers[v.Type] = k
   514  	}
   515  	m.ProviderLocalNames = providers
   516  }
   517  
   518  // LocalNameForProvider returns the module-specific user-supplied local name for
   519  // a given provider FQN, or the default local name if none was supplied.
   520  func (m *Module) LocalNameForProvider(p addrs.Provider) string {
   521  	if existing, exists := m.ProviderLocalNames[p]; exists {
   522  		return existing
   523  	} else {
   524  		// If there isn't a map entry, fall back to the default:
   525  		// Type = LocalName
   526  		return p.Type
   527  	}
   528  }
   529  
   530  // ProviderForLocalConfig returns the provider FQN for a given
   531  // LocalProviderConfig, based on its local name.
   532  func (m *Module) ProviderForLocalConfig(pc addrs.LocalProviderConfig) addrs.Provider {
   533  	return m.ImpliedProviderForUnqualifiedType(pc.LocalName)
   534  }
   535  
   536  // ImpliedProviderForUnqualifiedType returns the provider FQN for a given type,
   537  // first by looking up the type in the provider requirements map, and falling
   538  // back to an implied default provider.
   539  //
   540  // The intended behaviour is that configuring a provider with local name "foo"
   541  // in a required_providers block will result in resources with type "foo" using
   542  // that provider.
   543  func (m *Module) ImpliedProviderForUnqualifiedType(pType string) addrs.Provider {
   544  	if provider, exists := m.ProviderRequirements.RequiredProviders[pType]; exists {
   545  		return provider.Type
   546  	}
   547  	return addrs.ImpliedProviderForUnqualifiedType(pType)
   548  }