opentofu/config/module/validate_provider_alias.go
Mitchell Hashimoto 0c30caec7d
config: smarter provider alias usage validation
Fixes #4789

This improves the validation that valid provider aliases are used.

Previously, we required that provider aliases be defined in every module
they're used. This isn't correct because the alias may be used in a
parent module and inherited.

This removes that validation and creates the validation that a provider
alias must be defined in the used module or _any parent_. This allows
inheritance to work properly.

We've always had this type of validation for aliases because we believe
its a good UX tradeoff: typo-ing an alias is really painful, so we
require declaration of alias usage. It may add a small burden to
declare, but since relatively few aliases are used, it improves the
scenario where a user fat-fingers an alias name.
2016-12-16 16:47:32 -08:00

119 lines
2.9 KiB
Go

package module
import (
"fmt"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/dag"
)
// validateProviderAlias validates that all provider alias references are
// defined at some point in the parent tree. This improves UX by catching
// alias typos at the slight cost of requiring a declaration of usage. This
// is usually a good tradeoff since not many aliases are used.
func (t *Tree) validateProviderAlias() error {
// If we're not the root, don't perform this validation. We must be the
// root since we require full tree visibilty.
if len(t.path) != 0 {
return nil
}
// We'll use a graph to keep track of defined aliases at each level.
// As long as a parent defines an alias, it is okay.
var g dag.AcyclicGraph
t.buildProviderAliasGraph(&g, nil)
// Go through the graph and check that the usage is all good.
var err error
for _, v := range g.Vertices() {
pv, ok := v.(*providerAliasVertex)
if !ok {
// This shouldn't happen, just ignore it.
continue
}
// If we're not using any aliases, fast track and just continue
if len(pv.Used) == 0 {
continue
}
// Grab the ancestors since we're going to have to check if our
// parents define any of our aliases.
var parents []*providerAliasVertex
ancestors, _ := g.Ancestors(v)
for _, raw := range ancestors.List() {
if pv, ok := raw.(*providerAliasVertex); ok {
parents = append(parents, pv)
}
}
for k, _ := range pv.Used {
// Check if we define this
if _, ok := pv.Defined[k]; ok {
continue
}
// Check for a parent
found := false
for _, parent := range parents {
_, found = parent.Defined[k]
if found {
break
}
}
if found {
continue
}
// We didn't find the alias, error!
err = multierror.Append(err, fmt.Errorf(
"module %s: provider alias must be defined by the module or a parent: %s",
strings.Join(pv.Path, "."), k))
}
}
return err
}
func (t *Tree) buildProviderAliasGraph(g *dag.AcyclicGraph, parent dag.Vertex) {
// Add all our defined aliases
defined := make(map[string]struct{})
for _, p := range t.config.ProviderConfigs {
defined[p.FullName()] = struct{}{}
}
// Add all our used aliases
used := make(map[string]struct{})
for _, r := range t.config.Resources {
if r.Provider != "" {
used[r.Provider] = struct{}{}
}
}
// Add it to the graph
vertex := &providerAliasVertex{
Path: t.Path(),
Defined: defined,
Used: used,
}
g.Add(vertex)
// Connect to our parent if we have one
if parent != nil {
g.Connect(dag.BasicEdge(vertex, parent))
}
// Build all our children
for _, c := range t.Children() {
c.buildProviderAliasGraph(g, vertex)
}
}
// providerAliasVertex is the vertex for the graph that keeps track of
// defined provider aliases.
type providerAliasVertex struct {
Path []string
Defined map[string]struct{}
Used map[string]struct{}
}