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 }