]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/evaluate_valid.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / evaluate_valid.go
1 package terraform
2
3 import (
4 "fmt"
5 "sort"
6
7 "github.com/hashicorp/hcl2/hcl"
8
9 "github.com/hashicorp/terraform/addrs"
10 "github.com/hashicorp/terraform/configs"
11 "github.com/hashicorp/terraform/helper/didyoumean"
12 "github.com/hashicorp/terraform/tfdiags"
13 )
14
15 // StaticValidateReferences checks the given references against schemas and
16 // other statically-checkable rules, producing error diagnostics if any
17 // problems are found.
18 //
19 // If this method returns errors for a particular reference then evaluating
20 // that reference is likely to generate a very similar error, so callers should
21 // not run this method and then also evaluate the source expression(s) and
22 // merge the two sets of diagnostics together, since this will result in
23 // confusing redundant errors.
24 //
25 // This method can find more errors than can be found by evaluating an
26 // expression with a partially-populated scope, since it checks the referenced
27 // names directly against the schema rather than relying on evaluation errors.
28 //
29 // The result may include warning diagnostics if, for example, deprecated
30 // features are referenced.
31 func (d *evaluationStateData) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
32 var diags tfdiags.Diagnostics
33 for _, ref := range refs {
34 moreDiags := d.staticValidateReference(ref, self)
35 diags = diags.Append(moreDiags)
36 }
37 return diags
38 }
39
40 func (d *evaluationStateData) staticValidateReference(ref *addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
41 modCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
42 if modCfg == nil {
43 // This is a bug in the caller rather than a problem with the
44 // reference, but rather than crashing out here in an unhelpful way
45 // we'll just ignore it and trust a different layer to catch it.
46 return nil
47 }
48
49 if ref.Subject == addrs.Self {
50 // The "self" address is a special alias for the address given as
51 // our self parameter here, if present.
52 if self == nil {
53 var diags tfdiags.Diagnostics
54 diags = diags.Append(&hcl.Diagnostic{
55 Severity: hcl.DiagError,
56 Summary: `Invalid "self" reference`,
57 // This detail message mentions some current practice that
58 // this codepath doesn't really "know about". If the "self"
59 // object starts being supported in more contexts later then
60 // we'll need to adjust this message.
61 Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner and connection blocks.`,
62 Subject: ref.SourceRange.ToHCL().Ptr(),
63 })
64 return diags
65 }
66
67 synthRef := *ref // shallow copy
68 synthRef.Subject = self
69 ref = &synthRef
70 }
71
72 switch addr := ref.Subject.(type) {
73
74 // For static validation we validate both resource and resource instance references the same way.
75 // We mostly disregard the index, though we do some simple validation of
76 // its _presence_ in staticValidateSingleResourceReference and
77 // staticValidateMultiResourceReference respectively.
78 case addrs.Resource:
79 var diags tfdiags.Diagnostics
80 diags = diags.Append(d.staticValidateSingleResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
81 diags = diags.Append(d.staticValidateResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
82 return diags
83 case addrs.ResourceInstance:
84 var diags tfdiags.Diagnostics
85 diags = diags.Append(d.staticValidateMultiResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
86 diags = diags.Append(d.staticValidateResourceReference(modCfg, addr.ContainingResource(), ref.Remaining, ref.SourceRange))
87 return diags
88
89 // We also handle all module call references the same way, disregarding index.
90 case addrs.ModuleCall:
91 return d.staticValidateModuleCallReference(modCfg, addr, ref.Remaining, ref.SourceRange)
92 case addrs.ModuleCallInstance:
93 return d.staticValidateModuleCallReference(modCfg, addr.Call, ref.Remaining, ref.SourceRange)
94 case addrs.ModuleCallOutput:
95 // This one is a funny one because we will take the output name referenced
96 // and use it to fake up a "remaining" that would make sense for the
97 // module call itself, rather than for the specific output, and then
98 // we can just re-use our static module call validation logic.
99 remain := make(hcl.Traversal, len(ref.Remaining)+1)
100 copy(remain[1:], ref.Remaining)
101 remain[0] = hcl.TraverseAttr{
102 Name: addr.Name,
103
104 // Using the whole reference as the source range here doesn't exactly
105 // match how HCL would normally generate an attribute traversal,
106 // but is close enough for our purposes.
107 SrcRange: ref.SourceRange.ToHCL(),
108 }
109 return d.staticValidateModuleCallReference(modCfg, addr.Call.Call, remain, ref.SourceRange)
110
111 default:
112 // Anything else we'll just permit through without any static validation
113 // and let it be caught during dynamic evaluation, in evaluate.go .
114 return nil
115 }
116 }
117
118 func (d *evaluationStateData) staticValidateSingleResourceReference(modCfg *configs.Config, addr addrs.Resource, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
119 // If we have at least one step in "remain" and this resource has
120 // "count" set then we know for sure this in invalid because we have
121 // something like:
122 // aws_instance.foo.bar
123 // ...when we really need
124 // aws_instance.foo[count.index].bar
125
126 // It is _not_ safe to do this check when remain is empty, because that
127 // would also match aws_instance.foo[count.index].bar due to `count.index`
128 // not being statically-resolvable as part of a reference, and match
129 // direct references to the whole aws_instance.foo tuple.
130 if len(remain) == 0 {
131 return nil
132 }
133
134 var diags tfdiags.Diagnostics
135
136 cfg := modCfg.Module.ResourceByAddr(addr)
137 if cfg == nil {
138 // We'll just bail out here and catch this in our subsequent call to
139 // staticValidateResourceReference, then.
140 return diags
141 }
142
143 if cfg.Count != nil {
144 diags = diags.Append(&hcl.Diagnostic{
145 Severity: hcl.DiagError,
146 Summary: `Missing resource instance key`,
147 Detail: fmt.Sprintf("Because %s has \"count\" set, its attributes must be accessed on specific instances.\n\nFor example, to correlate with indices of a referring resource, use:\n %s[count.index]", addr, addr),
148 Subject: rng.ToHCL().Ptr(),
149 })
150 }
151 if cfg.ForEach != nil {
152 diags = diags.Append(&hcl.Diagnostic{
153 Severity: hcl.DiagError,
154 Summary: `Missing resource instance key`,
155 Detail: fmt.Sprintf("Because %s has \"for_each\" set, its attributes must be accessed on specific instances.\n\nFor example, to correlate with indices of a referring resource, use:\n %s[each.key]", addr, addr),
156 Subject: rng.ToHCL().Ptr(),
157 })
158 }
159
160 return diags
161 }
162
163 func (d *evaluationStateData) staticValidateMultiResourceReference(modCfg *configs.Config, addr addrs.ResourceInstance, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
164 var diags tfdiags.Diagnostics
165
166 cfg := modCfg.Module.ResourceByAddr(addr.ContainingResource())
167 if cfg == nil {
168 // We'll just bail out here and catch this in our subsequent call to
169 // staticValidateResourceReference, then.
170 return diags
171 }
172
173 if addr.Key == addrs.NoKey {
174 // This is a different path into staticValidateSingleResourceReference
175 return d.staticValidateSingleResourceReference(modCfg, addr.ContainingResource(), remain, rng)
176 } else {
177 if cfg.Count == nil && cfg.ForEach == nil {
178 diags = diags.Append(&hcl.Diagnostic{
179 Severity: hcl.DiagError,
180 Summary: `Unexpected resource instance key`,
181 Detail: fmt.Sprintf(`Because %s does not have "count" or "for_each" set, references to it must not include an index key. Remove the bracketed index to refer to the single instance of this resource.`, addr.ContainingResource()),
182 Subject: rng.ToHCL().Ptr(),
183 })
184 }
185 }
186
187 return diags
188 }
189
190 func (d *evaluationStateData) staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
191 var diags tfdiags.Diagnostics
192
193 var modeAdjective string
194 switch addr.Mode {
195 case addrs.ManagedResourceMode:
196 modeAdjective = "managed"
197 case addrs.DataResourceMode:
198 modeAdjective = "data"
199 default:
200 // should never happen
201 modeAdjective = "<invalid-mode>"
202 }
203
204 cfg := modCfg.Module.ResourceByAddr(addr)
205 if cfg == nil {
206 diags = diags.Append(&hcl.Diagnostic{
207 Severity: hcl.DiagError,
208 Summary: `Reference to undeclared resource`,
209 Detail: fmt.Sprintf(`A %s resource %q %q has not been declared in %s.`, modeAdjective, addr.Type, addr.Name, moduleConfigDisplayAddr(modCfg.Path)),
210 Subject: rng.ToHCL().Ptr(),
211 })
212 return diags
213 }
214
215 // Normally accessing this directly is wrong because it doesn't take into
216 // account provider inheritance, etc but it's okay here because we're only
217 // paying attention to the type anyway.
218 providerType := cfg.ProviderConfigAddr().Type
219 schema, _ := d.Evaluator.Schemas.ResourceTypeConfig(providerType, addr.Mode, addr.Type)
220
221 if schema == nil {
222 // Prior validation should've taken care of a resource block with an
223 // unsupported type, so we should never get here but we'll handle it
224 // here anyway for robustness.
225 diags = diags.Append(&hcl.Diagnostic{
226 Severity: hcl.DiagError,
227 Summary: `Invalid resource type`,
228 Detail: fmt.Sprintf(`A %s resource type %q is not supported by provider %q.`, modeAdjective, addr.Type, providerType),
229 Subject: rng.ToHCL().Ptr(),
230 })
231 return diags
232 }
233
234 // As a special case we'll detect attempts to access an attribute called
235 // "count" and produce a special error for it, since versions of Terraform
236 // prior to v0.12 offered this as a weird special case that we can no
237 // longer support.
238 if len(remain) > 0 {
239 if step, ok := remain[0].(hcl.TraverseAttr); ok && step.Name == "count" {
240 diags = diags.Append(&hcl.Diagnostic{
241 Severity: hcl.DiagError,
242 Summary: `Invalid resource count attribute`,
243 Detail: fmt.Sprintf(`The special "count" attribute is no longer supported after Terraform v0.12. Instead, use length(%s) to count resource instances.`, addr),
244 Subject: rng.ToHCL().Ptr(),
245 })
246 return diags
247 }
248 }
249
250 // If we got this far then we'll try to validate the remaining traversal
251 // steps against our schema.
252 moreDiags := schema.StaticValidateTraversal(remain)
253 diags = diags.Append(moreDiags)
254
255 return diags
256 }
257
258 func (d *evaluationStateData) staticValidateModuleCallReference(modCfg *configs.Config, addr addrs.ModuleCall, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
259 var diags tfdiags.Diagnostics
260
261 // For now, our focus here is just in testing that the referenced module
262 // call exists. All other validation is deferred until evaluation time.
263 _, exists := modCfg.Module.ModuleCalls[addr.Name]
264 if !exists {
265 var suggestions []string
266 for name := range modCfg.Module.ModuleCalls {
267 suggestions = append(suggestions, name)
268 }
269 sort.Strings(suggestions)
270 suggestion := didyoumean.NameSuggestion(addr.Name, suggestions)
271 if suggestion != "" {
272 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
273 }
274
275 diags = diags.Append(&hcl.Diagnostic{
276 Severity: hcl.DiagError,
277 Summary: `Reference to undeclared module`,
278 Detail: fmt.Sprintf(`No module call named %q is declared in %s.%s`, addr.Name, moduleConfigDisplayAddr(modCfg.Path), suggestion),
279 Subject: rng.ToHCL().Ptr(),
280 })
281 return diags
282 }
283
284 return diags
285 }
286
287 // moduleConfigDisplayAddr returns a string describing the given module
288 // address that is appropriate for returning to users in situations where the
289 // root module is possible. Specifically, it returns "the root module" if the
290 // root module instance is given, or a string representation of the module
291 // address otherwise.
292 func moduleConfigDisplayAddr(addr addrs.Module) string {
293 switch {
294 case addr.IsRoot():
295 return "the root module"
296 default:
297 return addr.String()
298 }
299 }