]>
Commit | Line | Data |
---|---|---|
1 | package module | |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strings" | |
6 | ||
7 | "github.com/hashicorp/go-multierror" | |
8 | "github.com/hashicorp/terraform/dag" | |
9 | ) | |
10 | ||
11 | // validateProviderAlias validates that all provider alias references are | |
12 | // defined at some point in the parent tree. This improves UX by catching | |
13 | // alias typos at the slight cost of requiring a declaration of usage. This | |
14 | // is usually a good tradeoff since not many aliases are used. | |
15 | func (t *Tree) validateProviderAlias() error { | |
16 | // If we're not the root, don't perform this validation. We must be the | |
17 | // root since we require full tree visibilty. | |
18 | if len(t.path) != 0 { | |
19 | return nil | |
20 | } | |
21 | ||
22 | // We'll use a graph to keep track of defined aliases at each level. | |
23 | // As long as a parent defines an alias, it is okay. | |
24 | var g dag.AcyclicGraph | |
25 | t.buildProviderAliasGraph(&g, nil) | |
26 | ||
27 | // Go through the graph and check that the usage is all good. | |
28 | var err error | |
29 | for _, v := range g.Vertices() { | |
30 | pv, ok := v.(*providerAliasVertex) | |
31 | if !ok { | |
32 | // This shouldn't happen, just ignore it. | |
33 | continue | |
34 | } | |
35 | ||
36 | // If we're not using any aliases, fast track and just continue | |
37 | if len(pv.Used) == 0 { | |
38 | continue | |
39 | } | |
40 | ||
41 | // Grab the ancestors since we're going to have to check if our | |
42 | // parents define any of our aliases. | |
43 | var parents []*providerAliasVertex | |
44 | ancestors, _ := g.Ancestors(v) | |
45 | for _, raw := range ancestors.List() { | |
46 | if pv, ok := raw.(*providerAliasVertex); ok { | |
47 | parents = append(parents, pv) | |
48 | } | |
49 | } | |
50 | for k, _ := range pv.Used { | |
51 | // Check if we define this | |
52 | if _, ok := pv.Defined[k]; ok { | |
53 | continue | |
54 | } | |
55 | ||
56 | // Check for a parent | |
57 | found := false | |
58 | for _, parent := range parents { | |
59 | _, found = parent.Defined[k] | |
60 | if found { | |
61 | break | |
62 | } | |
63 | } | |
64 | if found { | |
65 | continue | |
66 | } | |
67 | ||
68 | // We didn't find the alias, error! | |
69 | err = multierror.Append(err, fmt.Errorf( | |
70 | "module %s: provider alias must be defined by the module: %s", | |
71 | strings.Join(pv.Path, "."), k)) | |
72 | } | |
73 | } | |
74 | ||
75 | return err | |
76 | } | |
77 | ||
78 | func (t *Tree) buildProviderAliasGraph(g *dag.AcyclicGraph, parent dag.Vertex) { | |
79 | // Add all our defined aliases | |
80 | defined := make(map[string]struct{}) | |
81 | for _, p := range t.config.ProviderConfigs { | |
82 | defined[p.FullName()] = struct{}{} | |
83 | } | |
84 | ||
85 | // Add all our used aliases | |
86 | used := make(map[string]struct{}) | |
87 | for _, r := range t.config.Resources { | |
88 | if r.Provider != "" { | |
89 | used[r.Provider] = struct{}{} | |
90 | } | |
91 | } | |
92 | ||
93 | // Add it to the graph | |
94 | vertex := &providerAliasVertex{ | |
95 | Path: t.Path(), | |
96 | Defined: defined, | |
97 | Used: used, | |
98 | } | |
99 | g.Add(vertex) | |
100 | ||
101 | // Connect to our parent if we have one | |
102 | if parent != nil { | |
103 | g.Connect(dag.BasicEdge(vertex, parent)) | |
104 | } | |
105 | ||
106 | // Build all our children | |
107 | for _, c := range t.Children() { | |
108 | c.buildProviderAliasGraph(g, vertex) | |
109 | } | |
110 | } | |
111 | ||
112 | // providerAliasVertex is the vertex for the graph that keeps track of | |
113 | // defined provider aliases. | |
114 | type providerAliasVertex struct { | |
115 | Path []string | |
116 | Defined map[string]struct{} | |
117 | Used map[string]struct{} | |
118 | } |