-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10807 from hashicorp/b-alias-validate
config: smarter provider alias usage validation
- Loading branch information
Showing
9 changed files
with
184 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
resource "aws_instance" "foo" { | ||
provider = "aws.foo" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module "child" { | ||
source = "./child" | ||
} |
3 changes: 3 additions & 0 deletions
3
config/module/test-fixtures/validate-alias-good/child/main.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
resource "aws_instance" "foo" { | ||
provider = "aws.foo" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
provider "aws" { alias = "foo" } | ||
|
||
module "child" { | ||
source = "./child" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
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{} | ||
} |