github.com/rvichery/terraform@v0.11.10/configs/module.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl2/hcl"
     7  )
     8  
     9  // Module is a container for a set of configuration constructs that are
    10  // evaluated within a common namespace.
    11  type Module struct {
    12  	CoreVersionConstraints []VersionConstraint
    13  
    14  	Backend              *Backend
    15  	ProviderConfigs      map[string]*Provider
    16  	ProviderRequirements map[string][]VersionConstraint
    17  
    18  	Variables map[string]*Variable
    19  	Locals    map[string]*Local
    20  	Outputs   map[string]*Output
    21  
    22  	ModuleCalls map[string]*ModuleCall
    23  
    24  	ManagedResources map[string]*ManagedResource
    25  	DataResources    map[string]*DataResource
    26  }
    27  
    28  // File describes the contents of a single configuration file.
    29  //
    30  // Individual files are not usually used alone, but rather combined together
    31  // with other files (conventionally, those in the same directory) to produce
    32  // a *Module, using NewModule.
    33  //
    34  // At the level of an individual file we represent directly the structural
    35  // elements present in the file, without any attempt to detect conflicting
    36  // declarations. A File object can therefore be used for some basic static
    37  // analysis of individual elements, but must be built into a Module to detect
    38  // duplicate declarations.
    39  type File struct {
    40  	CoreVersionConstraints []VersionConstraint
    41  
    42  	Backends             []*Backend
    43  	ProviderConfigs      []*Provider
    44  	ProviderRequirements []*ProviderRequirement
    45  
    46  	Variables []*Variable
    47  	Locals    []*Local
    48  	Outputs   []*Output
    49  
    50  	ModuleCalls []*ModuleCall
    51  
    52  	ManagedResources []*ManagedResource
    53  	DataResources    []*DataResource
    54  }
    55  
    56  // NewModule takes a list of primary files and a list of override files and
    57  // produces a *Module by combining the files together.
    58  //
    59  // If there are any conflicting declarations in the given files -- for example,
    60  // if the same variable name is defined twice -- then the resulting module
    61  // will be incomplete and error diagnostics will be returned. Careful static
    62  // analysis of the returned Module is still possible in this case, but the
    63  // module will probably not be semantically valid.
    64  func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
    65  	var diags hcl.Diagnostics
    66  	mod := &Module{
    67  		ProviderConfigs:      map[string]*Provider{},
    68  		ProviderRequirements: map[string][]VersionConstraint{},
    69  		Variables:            map[string]*Variable{},
    70  		Locals:               map[string]*Local{},
    71  		Outputs:              map[string]*Output{},
    72  		ModuleCalls:          map[string]*ModuleCall{},
    73  		ManagedResources:     map[string]*ManagedResource{},
    74  		DataResources:        map[string]*DataResource{},
    75  	}
    76  
    77  	for _, file := range primaryFiles {
    78  		fileDiags := mod.appendFile(file)
    79  		diags = append(diags, fileDiags...)
    80  	}
    81  
    82  	for _, file := range overrideFiles {
    83  		fileDiags := mod.mergeFile(file)
    84  		diags = append(diags, fileDiags...)
    85  	}
    86  
    87  	return mod, diags
    88  }
    89  
    90  func (m *Module) appendFile(file *File) hcl.Diagnostics {
    91  	var diags hcl.Diagnostics
    92  
    93  	for _, constraint := range file.CoreVersionConstraints {
    94  		// If there are any conflicting requirements then we'll catch them
    95  		// when we actually check these constraints.
    96  		m.CoreVersionConstraints = append(m.CoreVersionConstraints, constraint)
    97  	}
    98  
    99  	for _, b := range file.Backends {
   100  		if m.Backend != nil {
   101  			diags = append(diags, &hcl.Diagnostic{
   102  				Severity: hcl.DiagError,
   103  				Summary:  "Duplicate backend configuration",
   104  				Detail:   fmt.Sprintf("A module may have only one backend configuration. The backend was previously configured at %s.", m.Backend.DeclRange),
   105  				Subject:  &b.DeclRange,
   106  			})
   107  			continue
   108  		}
   109  		m.Backend = b
   110  	}
   111  
   112  	for _, pc := range file.ProviderConfigs {
   113  		key := pc.moduleUniqueKey()
   114  		if existing, exists := m.ProviderConfigs[key]; exists {
   115  			if existing.Alias == "" {
   116  				diags = append(diags, &hcl.Diagnostic{
   117  					Severity: hcl.DiagError,
   118  					Summary:  "Duplicate provider configuration",
   119  					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),
   120  					Subject:  &pc.DeclRange,
   121  				})
   122  			} else {
   123  				diags = append(diags, &hcl.Diagnostic{
   124  					Severity: hcl.DiagError,
   125  					Summary:  "Duplicate provider configuration",
   126  					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),
   127  					Subject:  &pc.DeclRange,
   128  				})
   129  			}
   130  			continue
   131  		}
   132  		m.ProviderConfigs[key] = pc
   133  	}
   134  
   135  	for _, reqd := range file.ProviderRequirements {
   136  		m.ProviderRequirements[reqd.Name] = append(m.ProviderRequirements[reqd.Name], reqd.Requirement)
   137  	}
   138  
   139  	for _, v := range file.Variables {
   140  		if existing, exists := m.Variables[v.Name]; exists {
   141  			diags = append(diags, &hcl.Diagnostic{
   142  				Severity: hcl.DiagError,
   143  				Summary:  "Duplicate variable declaration",
   144  				Detail:   fmt.Sprintf("A variable named %q was already declared at %s. Variable names must be unique within a module.", existing.Name, existing.DeclRange),
   145  				Subject:  &v.DeclRange,
   146  			})
   147  		}
   148  		m.Variables[v.Name] = v
   149  	}
   150  
   151  	for _, l := range file.Locals {
   152  		if existing, exists := m.Locals[l.Name]; exists {
   153  			diags = append(diags, &hcl.Diagnostic{
   154  				Severity: hcl.DiagError,
   155  				Summary:  "Duplicate local value definition",
   156  				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),
   157  				Subject:  &l.DeclRange,
   158  			})
   159  		}
   160  		m.Locals[l.Name] = l
   161  	}
   162  
   163  	for _, o := range file.Outputs {
   164  		if existing, exists := m.Outputs[o.Name]; exists {
   165  			diags = append(diags, &hcl.Diagnostic{
   166  				Severity: hcl.DiagError,
   167  				Summary:  "Duplicate output definition",
   168  				Detail:   fmt.Sprintf("An output named %q was already defined at %s. Output names must be unique within a module.", existing.Name, existing.DeclRange),
   169  				Subject:  &o.DeclRange,
   170  			})
   171  		}
   172  		m.Outputs[o.Name] = o
   173  	}
   174  
   175  	for _, mc := range file.ModuleCalls {
   176  		if existing, exists := m.ModuleCalls[mc.Name]; exists {
   177  			diags = append(diags, &hcl.Diagnostic{
   178  				Severity: hcl.DiagError,
   179  				Summary:  "Duplicate module call",
   180  				Detail:   fmt.Sprintf("An module call named %q was already defined at %s. Module calls must have unique names within a module.", existing.Name, existing.DeclRange),
   181  				Subject:  &mc.DeclRange,
   182  			})
   183  		}
   184  		m.ModuleCalls[mc.Name] = mc
   185  	}
   186  
   187  	for _, r := range file.ManagedResources {
   188  		key := r.moduleUniqueKey()
   189  		if existing, exists := m.ManagedResources[key]; exists {
   190  			diags = append(diags, &hcl.Diagnostic{
   191  				Severity: hcl.DiagError,
   192  				Summary:  fmt.Sprintf("Duplicate resource %q configuration", existing.Type),
   193  				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),
   194  				Subject:  &r.DeclRange,
   195  			})
   196  			continue
   197  		}
   198  		m.ManagedResources[key] = r
   199  	}
   200  
   201  	for _, r := range file.DataResources {
   202  		key := r.moduleUniqueKey()
   203  		if existing, exists := m.DataResources[key]; exists {
   204  			diags = append(diags, &hcl.Diagnostic{
   205  				Severity: hcl.DiagError,
   206  				Summary:  fmt.Sprintf("Duplicate data %q configuration", existing.Type),
   207  				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),
   208  				Subject:  &r.DeclRange,
   209  			})
   210  			continue
   211  		}
   212  		m.DataResources[key] = r
   213  	}
   214  
   215  	return diags
   216  }
   217  
   218  func (m *Module) mergeFile(file *File) hcl.Diagnostics {
   219  	var diags hcl.Diagnostics
   220  
   221  	if len(file.CoreVersionConstraints) != 0 {
   222  		// This is a bit of a strange case for overriding since we normally
   223  		// would union together across multiple files anyway, but we'll
   224  		// allow it and have each override file clobber any existing list.
   225  		m.CoreVersionConstraints = nil
   226  		for _, constraint := range file.CoreVersionConstraints {
   227  			m.CoreVersionConstraints = append(m.CoreVersionConstraints, constraint)
   228  		}
   229  	}
   230  
   231  	if len(file.Backends) != 0 {
   232  		switch len(file.Backends) {
   233  		case 1:
   234  			m.Backend = file.Backends[0]
   235  		default:
   236  			// An override file with multiple backends is still invalid, even
   237  			// though it can override backends from _other_ files.
   238  			diags = append(diags, &hcl.Diagnostic{
   239  				Severity: hcl.DiagError,
   240  				Summary:  "Duplicate backend configuration",
   241  				Detail:   fmt.Sprintf("Each override file may have only one backend configuration. A backend was previously configured at %s.", file.Backends[0].DeclRange),
   242  				Subject:  &file.Backends[1].DeclRange,
   243  			})
   244  		}
   245  	}
   246  
   247  	for _, pc := range file.ProviderConfigs {
   248  		key := pc.moduleUniqueKey()
   249  		existing, exists := m.ProviderConfigs[key]
   250  		if pc.Alias == "" {
   251  			// We allow overriding a non-existing _default_ provider configuration
   252  			// because the user model is that an absent provider configuration
   253  			// implies an empty provider configuration, which is what the user
   254  			// is therefore overriding here.
   255  			if exists {
   256  				mergeDiags := existing.merge(pc)
   257  				diags = append(diags, mergeDiags...)
   258  			} else {
   259  				m.ProviderConfigs[key] = pc
   260  			}
   261  		} else {
   262  			// For aliased providers, there must be a base configuration to
   263  			// override. This allows us to detect and report alias typos
   264  			// that might otherwise cause the override to not apply.
   265  			if !exists {
   266  				diags = append(diags, &hcl.Diagnostic{
   267  					Severity: hcl.DiagError,
   268  					Summary:  "Missing base provider configuration for override",
   269  					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),
   270  					Subject:  &pc.DeclRange,
   271  				})
   272  				continue
   273  			}
   274  			mergeDiags := existing.merge(pc)
   275  			diags = append(diags, mergeDiags...)
   276  		}
   277  	}
   278  
   279  	if len(file.ProviderRequirements) != 0 {
   280  		mergeProviderVersionConstraints(m.ProviderRequirements, file.ProviderRequirements)
   281  	}
   282  
   283  	for _, v := range file.Variables {
   284  		existing, exists := m.Variables[v.Name]
   285  		if !exists {
   286  			diags = append(diags, &hcl.Diagnostic{
   287  				Severity: hcl.DiagError,
   288  				Summary:  "Missing base variable declaration to override",
   289  				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),
   290  				Subject:  &v.DeclRange,
   291  			})
   292  			continue
   293  		}
   294  		mergeDiags := existing.merge(v)
   295  		diags = append(diags, mergeDiags...)
   296  	}
   297  
   298  	for _, l := range file.Locals {
   299  		existing, exists := m.Locals[l.Name]
   300  		if !exists {
   301  			diags = append(diags, &hcl.Diagnostic{
   302  				Severity: hcl.DiagError,
   303  				Summary:  "Missing base local value definition to override",
   304  				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),
   305  				Subject:  &l.DeclRange,
   306  			})
   307  			continue
   308  		}
   309  		mergeDiags := existing.merge(l)
   310  		diags = append(diags, mergeDiags...)
   311  	}
   312  
   313  	for _, o := range file.Outputs {
   314  		existing, exists := m.Outputs[o.Name]
   315  		if !exists {
   316  			diags = append(diags, &hcl.Diagnostic{
   317  				Severity: hcl.DiagError,
   318  				Summary:  "Missing base output definition to override",
   319  				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),
   320  				Subject:  &o.DeclRange,
   321  			})
   322  			continue
   323  		}
   324  		mergeDiags := existing.merge(o)
   325  		diags = append(diags, mergeDiags...)
   326  	}
   327  
   328  	for _, mc := range file.ModuleCalls {
   329  		existing, exists := m.ModuleCalls[mc.Name]
   330  		if !exists {
   331  			diags = append(diags, &hcl.Diagnostic{
   332  				Severity: hcl.DiagError,
   333  				Summary:  "Missing module call to override",
   334  				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),
   335  				Subject:  &mc.DeclRange,
   336  			})
   337  			continue
   338  		}
   339  		mergeDiags := existing.merge(mc)
   340  		diags = append(diags, mergeDiags...)
   341  	}
   342  
   343  	for _, r := range file.ManagedResources {
   344  		key := r.moduleUniqueKey()
   345  		existing, exists := m.ManagedResources[key]
   346  		if !exists {
   347  			diags = append(diags, &hcl.Diagnostic{
   348  				Severity: hcl.DiagError,
   349  				Summary:  "Missing resource to override",
   350  				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),
   351  				Subject:  &r.DeclRange,
   352  			})
   353  			continue
   354  		}
   355  		mergeDiags := existing.merge(r)
   356  		diags = append(diags, mergeDiags...)
   357  	}
   358  
   359  	for _, r := range file.DataResources {
   360  		key := r.moduleUniqueKey()
   361  		existing, exists := m.DataResources[key]
   362  		if !exists {
   363  			diags = append(diags, &hcl.Diagnostic{
   364  				Severity: hcl.DiagError,
   365  				Summary:  "Missing data resource to override",
   366  				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),
   367  				Subject:  &r.DeclRange,
   368  			})
   369  			continue
   370  		}
   371  		mergeDiags := existing.merge(r)
   372  		diags = append(diags, mergeDiags...)
   373  	}
   374  
   375  	return diags
   376  }