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  }