forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprovider_validation.go
243 lines (205 loc) · 8.03 KB
/
provider_validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package configs
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs"
)
// validateProviderConfigs walks the full configuration tree from the root
// module outward, static validation rules to the various combinations of
// provider configuration, required_providers values, and module call providers
// mappings.
//
// To retain compatibility with previous terraform versions, empty "proxy
// provider blocks" are still allowed within modules, though they will
// generate warnings when the configuration is loaded. The new validation
// however will generate an error if a suitable provider configuration is not
// passed in through the module call.
//
// The call argument is the ModuleCall for the provided Config cfg. The
// noProviderConfig argument is passed down the call stack, indicating that the
// module call, or a parent module call, has used a feature that precludes
// providers from being configured at all within the module.
func validateProviderConfigs(call *ModuleCall, cfg *Config, noProviderConfig bool) (diags hcl.Diagnostics) {
for name, child := range cfg.Children {
mc := cfg.Module.ModuleCalls[name]
// if the module call has any of count, for_each or depends_on,
// providers are prohibited from being configured in this module, or
// any module beneath this module.
nope := noProviderConfig || mc.Count != nil || mc.ForEach != nil || mc.DependsOn != nil
diags = append(diags, validateProviderConfigs(mc, child, nope)...)
}
// nothing else to do in the root module
if call == nil {
return diags
}
// the set of provider configuration names passed into the module, with the
// source range of the provider assignment in the module call.
passedIn := map[string]PassedProviderConfig{}
// the set of empty configurations that could be proxy configurations, with
// the source range of the empty configuration block.
emptyConfigs := map[string]*hcl.Range{}
// the set of provider with a defined configuration, with the source range
// of the configuration block declaration.
configured := map[string]*hcl.Range{}
// the set of configuration_aliases defined in the required_providers
// block, with the fully qualified provider type.
configAliases := map[string]addrs.AbsProviderConfig{}
// the set of provider names defined in the required_providers block, and
// their provider types.
localNames := map[string]addrs.AbsProviderConfig{}
for _, passed := range call.Providers {
name := providerName(passed.InChild.Name, passed.InChild.Alias)
passedIn[name] = passed
}
mod := cfg.Module
for _, pc := range mod.ProviderConfigs {
name := providerName(pc.Name, pc.Alias)
// Validate the config against an empty schema to see if it's empty.
_, pcConfigDiags := pc.Config.Content(&hcl.BodySchema{})
if pcConfigDiags.HasErrors() || pc.Version.Required != nil {
configured[name] = &pc.DeclRange
} else {
emptyConfigs[name] = &pc.DeclRange
}
}
if mod.ProviderRequirements != nil {
for _, req := range mod.ProviderRequirements.RequiredProviders {
addr := addrs.AbsProviderConfig{
Module: cfg.Path,
Provider: req.Type,
}
localNames[req.Name] = addr
for _, alias := range req.Aliases {
addr := addrs.AbsProviderConfig{
Module: cfg.Path,
Provider: req.Type,
Alias: alias.Alias,
}
configAliases[providerName(alias.LocalName, alias.Alias)] = addr
}
}
}
// there cannot be any configurations if no provider config is allowed
if len(configured) > 0 && noProviderConfig {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Module %s contains provider configuration", cfg.Path),
Detail: "Providers cannot be configured within modules using count, for_each or depends_on.",
})
}
// now check that the user is not attempting to override a config
for name := range configured {
if passed, ok := passedIn[name]; ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Cannot override provider configuration",
Detail: fmt.Sprintf("Provider %s is configured within the module %s and cannot be overridden.", name, cfg.Path),
Subject: &passed.InChild.NameRange,
})
}
}
// A declared alias requires either a matching configuration within the
// module, or one must be passed in.
for name, providerAddr := range configAliases {
_, confOk := configured[name]
_, passedOk := passedIn[name]
if confOk || passedOk {
continue
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("No configuration for provider %s", name),
Detail: fmt.Sprintf("Configuration required for %s.", providerAddr),
Subject: &call.DeclRange,
})
}
// You cannot pass in a provider that cannot be used
for name, passed := range passedIn {
providerAddr := addrs.AbsProviderConfig{
Module: cfg.Path,
Provider: addrs.NewDefaultProvider(passed.InChild.Name),
Alias: passed.InChild.Alias,
}
localAddr, localName := localNames[name]
if localName {
providerAddr = localAddr
}
aliasAddr, configAlias := configAliases[name]
if configAlias {
providerAddr = aliasAddr
}
_, emptyConfig := emptyConfigs[name]
if !(localName || configAlias || emptyConfig) {
severity := hcl.DiagError
// we still allow default configs, so switch to a warning if the incoming provider is a default
if providerAddr.Provider.IsDefault() {
severity = hcl.DiagWarning
}
diags = append(diags, &hcl.Diagnostic{
Severity: severity,
Summary: fmt.Sprintf("Provider %s is undefined", name),
Detail: fmt.Sprintf("Module %s does not declare a provider named %s.\n", cfg.Path, name) +
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),
Subject: &passed.InChild.NameRange,
})
}
// The provider being passed in must also be of the correct type.
// While we would like to ensure required_providers exists here,
// implied default configuration is still allowed.
pTy := addrs.NewDefaultProvider(passed.InParent.Name)
// use the full address for a nice diagnostic output
parentAddr := addrs.AbsProviderConfig{
Module: cfg.Parent.Path,
Provider: pTy,
Alias: passed.InParent.Alias,
}
if cfg.Parent.Module.ProviderRequirements != nil {
req, defined := cfg.Parent.Module.ProviderRequirements.RequiredProviders[name]
if defined {
parentAddr.Provider = req.Type
}
}
if !providerAddr.Provider.Equals(parentAddr.Provider) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Invalid type for provider %s", providerAddr),
Detail: fmt.Sprintf("Cannot use configuration from %s for %s. ", parentAddr, providerAddr) +
"The given provider configuration is for a different provider type.",
Subject: &passed.InChild.NameRange,
})
}
}
// Empty configurations are no longer needed
for name, src := range emptyConfigs {
detail := fmt.Sprintf("Remove the %s provider block from %s.", name, cfg.Path)
isAlias := strings.Contains(name, ".")
_, isConfigAlias := configAliases[name]
_, isLocalName := localNames[name]
if isAlias && !isConfigAlias {
localName := strings.Split(name, ".")[0]
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)
}
if !isAlias && !isLocalName {
// if there is no local name, add a note to include it in the
// required_provider block
detail += fmt.Sprintf("\nTo ensure the correct provider configuration is used, add %s to the required_providers configuration", name)
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Empty provider configuration blocks are not required",
Detail: detail,
Subject: src,
})
}
if diags.HasErrors() {
return diags
}
return diags
}
func providerName(name, alias string) string {
if alias != "" {
name = name + "." + alias
}
return name
}