github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/configs/module.go (about)

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