7 "github.com/hashicorp/hcl2/hcl"
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"
15 // StaticValidateReferences checks the given references against schemas and
16 // other statically-checkable rules, producing error diagnostics if any
17 // problems are found.
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.
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.
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)
40 func (d *evaluationStateData) staticValidateReference(ref *addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
41 modCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
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.
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.
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(),
67 synthRef := *ref // shallow copy
68 synthRef.Subject = self
72 switch addr := ref.Subject.(type) {
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.
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))
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))
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{
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(),
109 return d.staticValidateModuleCallReference(modCfg, addr.Call.Call, remain, ref.SourceRange)
112 // Anything else we'll just permit through without any static validation
113 // and let it be caught during dynamic evaluation, in evaluate.go .
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
122 // aws_instance.foo.bar
123 // ...when we really need
124 // aws_instance.foo[count.index].bar
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 {
134 var diags tfdiags.Diagnostics
136 cfg := modCfg.Module.ResourceByAddr(addr)
138 // We'll just bail out here and catch this in our subsequent call to
139 // staticValidateResourceReference, then.
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(),
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(),
163 func (d *evaluationStateData) staticValidateMultiResourceReference(modCfg *configs.Config, addr addrs.ResourceInstance, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
164 var diags tfdiags.Diagnostics
166 cfg := modCfg.Module.ResourceByAddr(addr.ContainingResource())
168 // We'll just bail out here and catch this in our subsequent call to
169 // staticValidateResourceReference, then.
173 if addr.Key == addrs.NoKey {
174 // This is a different path into staticValidateSingleResourceReference
175 return d.staticValidateSingleResourceReference(modCfg, addr.ContainingResource(), remain, rng)
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(),
190 func (d *evaluationStateData) staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
191 var diags tfdiags.Diagnostics
193 var modeAdjective string
195 case addrs.ManagedResourceMode:
196 modeAdjective = "managed"
197 case addrs.DataResourceMode:
198 modeAdjective = "data"
200 // should never happen
201 modeAdjective = "<invalid-mode>"
204 cfg := modCfg.Module.ResourceByAddr(addr)
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(),
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)
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(),
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
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(),
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)
258 func (d *evaluationStateData) staticValidateModuleCallReference(modCfg *configs.Config, addr addrs.ModuleCall, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
259 var diags tfdiags.Diagnostics
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]
265 var suggestions []string
266 for name := range modCfg.Module.ModuleCalls {
267 suggestions = append(suggestions, name)
269 sort.Strings(suggestions)
270 suggestion := didyoumean.NameSuggestion(addr.Name, suggestions)
271 if suggestion != "" {
272 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
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(),
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 {
295 return "the root module"