kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/provider_validation.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "kubeform.dev/terraform-backend-sdk/addrs" 9 ) 10 11 // validateProviderConfigs walks the full configuration tree from the root 12 // module outward, static validation rules to the various combinations of 13 // provider configuration, required_providers values, and module call providers 14 // mappings. 15 // 16 // To retain compatibility with previous terraform versions, empty "proxy 17 // provider blocks" are still allowed within modules, though they will 18 // generate warnings when the configuration is loaded. The new validation 19 // however will generate an error if a suitable provider configuration is not 20 // passed in through the module call. 21 // 22 // The call argument is the ModuleCall for the provided Config cfg. The 23 // noProviderConfig argument is passed down the call stack, indicating that the 24 // module call, or a parent module call, has used a feature that precludes 25 // providers from being configured at all within the module. 26 func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConfig bool) (diags hcl.Diagnostics) { 27 mod := cfg.Module 28 29 for name, child := range cfg.Children { 30 mc := mod.ModuleCalls[name] 31 32 // if the module call has any of count, for_each or depends_on, 33 // providers are prohibited from being configured in this module, or 34 // any module beneath this module. 35 nope := noProviderConfig || mc.Count != nil || mc.ForEach != nil || mc.DependsOn != nil 36 diags = append(diags, validateProviderConfigs(mc, child, nope)...) 37 } 38 39 // the set of provider configuration names passed into the module, with the 40 // source range of the provider assignment in the module call. 41 passedIn := map[string]PassedProviderConfig{} 42 43 // the set of empty configurations that could be proxy configurations, with 44 // the source range of the empty configuration block. 45 emptyConfigs := map[string]*hcl.Range{} 46 47 // the set of provider with a defined configuration, with the source range 48 // of the configuration block declaration. 49 configured := map[string]*hcl.Range{} 50 51 // the set of configuration_aliases defined in the required_providers 52 // block, with the fully qualified provider type. 53 configAliases := map[string]addrs.AbsProviderConfig{} 54 55 // the set of provider names defined in the required_providers block, and 56 // their provider types. 57 localNames := map[string]addrs.AbsProviderConfig{} 58 59 for _, pc := range mod.ProviderConfigs { 60 name := providerName(pc.Name, pc.Alias) 61 // Validate the config against an empty schema to see if it's empty. 62 _, pcConfigDiags := pc.Config.Content(&hcl.BodySchema{}) 63 if pcConfigDiags.HasErrors() || pc.Version.Required != nil { 64 configured[name] = &pc.DeclRange 65 } else { 66 emptyConfigs[name] = &pc.DeclRange 67 } 68 } 69 70 if mod.ProviderRequirements != nil { 71 for _, req := range mod.ProviderRequirements.RequiredProviders { 72 addr := addrs.AbsProviderConfig{ 73 Module: cfg.Path, 74 Provider: req.Type, 75 } 76 localNames[req.Name] = addr 77 for _, alias := range req.Aliases { 78 addr := addrs.AbsProviderConfig{ 79 Module: cfg.Path, 80 Provider: req.Type, 81 Alias: alias.Alias, 82 } 83 configAliases[providerName(alias.LocalName, alias.Alias)] = addr 84 } 85 } 86 } 87 88 // collect providers passed from the parent 89 if parentCall != nil { 90 for _, passed := range parentCall.Providers { 91 name := providerName(passed.InChild.Name, passed.InChild.Alias) 92 passedIn[name] = passed 93 } 94 } 95 96 parentModuleText := "the root module" 97 moduleText := "the root module" 98 if !cfg.Path.IsRoot() { 99 moduleText = cfg.Path.String() 100 if parent := cfg.Path.Parent(); !parent.IsRoot() { 101 // module address are prefixed with `module.` 102 parentModuleText = parent.String() 103 } 104 } 105 106 // Verify that any module calls only refer to named providers, and that 107 // those providers will have a configuration at runtime. This way we can 108 // direct users where to add the missing configuration, because the runtime 109 // error is only "missing provider X". 110 for _, modCall := range mod.ModuleCalls { 111 for _, passed := range modCall.Providers { 112 // aliased providers are handled more strictly, and are never 113 // inherited, so they are validated within modules further down. 114 // Skip these checks to prevent redundant diagnostics. 115 if passed.InParent.Alias != "" { 116 continue 117 } 118 119 name := passed.InParent.String() 120 _, confOK := configured[name] 121 _, localOK := localNames[name] 122 _, passedOK := passedIn[name] 123 124 // This name was not declared somewhere within in the 125 // configuration. We ignore empty configs, because they will 126 // already produce a warning. 127 if !(confOK || localOK) { 128 diags = append(diags, &hcl.Diagnostic{ 129 Severity: hcl.DiagWarning, 130 Summary: fmt.Sprintf("Provider %s is undefined", name), 131 Detail: fmt.Sprintf("No provider named %s has been declared in %s.\n", name, moduleText) + 132 fmt.Sprintf("If you wish to refer to the %s provider within the module, add a provider configuration, or an entry in the required_providers block.", name), 133 Subject: &passed.InParent.NameRange, 134 }) 135 continue 136 } 137 138 // Now we may have named this provider within the module, but 139 // there won't be a configuration available at runtime if the 140 // parent module did not pass one in. 141 if !cfg.Path.IsRoot() && !(confOK || passedOK) { 142 diags = append(diags, &hcl.Diagnostic{ 143 Severity: hcl.DiagWarning, 144 Summary: fmt.Sprintf("No configuration passed in for provider %s in %s", name, cfg.Path), 145 Detail: fmt.Sprintf("Provider %s is referenced within %s, but no configuration has been supplied.\n", name, moduleText) + 146 fmt.Sprintf("Add a provider named %s to the providers map for %s in %s.", name, cfg.Path, parentModuleText), 147 Subject: &passed.InParent.NameRange, 148 }) 149 } 150 } 151 } 152 153 if cfg.Path.IsRoot() { 154 // nothing else to do in the root module 155 return diags 156 } 157 158 // there cannot be any configurations if no provider config is allowed 159 if len(configured) > 0 && noProviderConfig { 160 diags = append(diags, &hcl.Diagnostic{ 161 Severity: hcl.DiagError, 162 Summary: fmt.Sprintf("Module %s contains provider configuration", cfg.Path), 163 Detail: "Providers cannot be configured within modules using count, for_each or depends_on.", 164 }) 165 } 166 167 // now check that the user is not attempting to override a config 168 for name := range configured { 169 if passed, ok := passedIn[name]; ok { 170 diags = append(diags, &hcl.Diagnostic{ 171 Severity: hcl.DiagError, 172 Summary: "Cannot override provider configuration", 173 Detail: fmt.Sprintf("Provider %s is configured within the module %s and cannot be overridden.", name, cfg.Path), 174 Subject: &passed.InChild.NameRange, 175 }) 176 } 177 } 178 179 // A declared alias requires either a matching configuration within the 180 // module, or one must be passed in. 181 for name, providerAddr := range configAliases { 182 _, confOk := configured[name] 183 _, passedOk := passedIn[name] 184 185 if confOk || passedOk { 186 continue 187 } 188 189 diags = append(diags, &hcl.Diagnostic{ 190 Severity: hcl.DiagError, 191 Summary: fmt.Sprintf("No configuration for provider %s", name), 192 Detail: fmt.Sprintf("Configuration required for %s.\n", providerAddr) + 193 fmt.Sprintf("Add a provider named %s to the providers map for %s in %s.", name, cfg.Path, parentModuleText), 194 Subject: &parentCall.DeclRange, 195 }) 196 } 197 198 // You cannot pass in a provider that cannot be used 199 for name, passed := range passedIn { 200 childTy := passed.InChild.providerType 201 // get a default type if there was none set 202 if childTy.IsZero() { 203 // This means the child module is only using an inferred 204 // provider type. We allow this but will generate a warning to 205 // declare provider_requirements below. 206 childTy = addrs.NewDefaultProvider(passed.InChild.Name) 207 } 208 209 providerAddr := addrs.AbsProviderConfig{ 210 Module: cfg.Path, 211 Provider: childTy, 212 Alias: passed.InChild.Alias, 213 } 214 215 localAddr, localName := localNames[name] 216 if localName { 217 providerAddr = localAddr 218 } 219 220 aliasAddr, configAlias := configAliases[name] 221 if configAlias { 222 providerAddr = aliasAddr 223 } 224 225 _, emptyConfig := emptyConfigs[name] 226 227 if !(localName || configAlias || emptyConfig) { 228 severity := hcl.DiagError 229 230 // we still allow default configs, so switch to a warning if the incoming provider is a default 231 if providerAddr.Provider.IsDefault() { 232 severity = hcl.DiagWarning 233 } 234 235 diags = append(diags, &hcl.Diagnostic{ 236 Severity: severity, 237 Summary: fmt.Sprintf("Provider %s is undefined", name), 238 Detail: fmt.Sprintf("Module %s does not declare a provider named %s.\n", cfg.Path, name) + 239 fmt.Sprintf("If you wish to specify a provider configuration for the module, add an entry for %s in the required_providers block within the module.", name), 240 Subject: &passed.InChild.NameRange, 241 }) 242 } 243 244 // The provider being passed in must also be of the correct type. 245 pTy := passed.InParent.providerType 246 if pTy.IsZero() { 247 // While we would like to ensure required_providers exists here, 248 // implied default configuration is still allowed. 249 pTy = addrs.NewDefaultProvider(passed.InParent.Name) 250 } 251 252 // use the full address for a nice diagnostic output 253 parentAddr := addrs.AbsProviderConfig{ 254 Module: cfg.Parent.Path, 255 Provider: pTy, 256 Alias: passed.InParent.Alias, 257 } 258 259 if cfg.Parent.Module.ProviderRequirements != nil { 260 req, defined := cfg.Parent.Module.ProviderRequirements.RequiredProviders[name] 261 if defined { 262 parentAddr.Provider = req.Type 263 } 264 } 265 266 if !providerAddr.Provider.Equals(parentAddr.Provider) { 267 diags = append(diags, &hcl.Diagnostic{ 268 Severity: hcl.DiagError, 269 Summary: fmt.Sprintf("Invalid type for provider %s", providerAddr), 270 Detail: fmt.Sprintf("Cannot use configuration from %s for %s. ", parentAddr, providerAddr) + 271 "The given provider configuration is for a different provider type.", 272 Subject: &passed.InChild.NameRange, 273 }) 274 } 275 } 276 277 // Empty configurations are no longer needed 278 for name, src := range emptyConfigs { 279 detail := fmt.Sprintf("Remove the %s provider block from %s.", name, cfg.Path) 280 281 isAlias := strings.Contains(name, ".") 282 _, isConfigAlias := configAliases[name] 283 _, isLocalName := localNames[name] 284 285 if isAlias && !isConfigAlias { 286 localName := strings.Split(name, ".")[0] 287 detail = fmt.Sprintf("Remove the %s provider block from %s. Add %s to the list of configuration_aliases for %s in required_providers to define the provider configuration name.", name, cfg.Path, name, localName) 288 } 289 290 if !isAlias && !isLocalName { 291 // if there is no local name, add a note to include it in the 292 // required_provider block 293 detail += fmt.Sprintf("\nTo ensure the correct provider configuration is used, add %s to the required_providers configuration", name) 294 } 295 296 diags = append(diags, &hcl.Diagnostic{ 297 Severity: hcl.DiagWarning, 298 Summary: "Empty provider configuration blocks are not required", 299 Detail: detail, 300 Subject: src, 301 }) 302 } 303 304 return diags 305 } 306 307 func providerName(name, alias string) string { 308 if alias != "" { 309 name = name + "." + alias 310 } 311 return name 312 }