github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/evaluate_valid.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/hashicorp/hcl/v2" 8 9 "github.com/muratcelep/terraform/not-internal/addrs" 10 "github.com/muratcelep/terraform/not-internal/configs" 11 "github.com/muratcelep/terraform/not-internal/didyoumean" 12 "github.com/muratcelep/terraform/not-internal/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.ModuleCallInstanceOutput: 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 var suggestion string 207 // A common mistake is omitting the data. prefix when trying to refer 208 // to a data resource, so we'll add a special hint for that. 209 if addr.Mode == addrs.ManagedResourceMode { 210 candidateAddr := addr // not a pointer, so this is a copy 211 candidateAddr.Mode = addrs.DataResourceMode 212 if candidateCfg := modCfg.Module.ResourceByAddr(candidateAddr); candidateCfg != nil { 213 suggestion = fmt.Sprintf("\n\nDid you mean the data resource %s?", candidateAddr) 214 } 215 } 216 217 diags = diags.Append(&hcl.Diagnostic{ 218 Severity: hcl.DiagError, 219 Summary: `Reference to undeclared resource`, 220 Detail: fmt.Sprintf(`A %s resource %q %q has not been declared in %s.%s`, modeAdjective, addr.Type, addr.Name, moduleConfigDisplayAddr(modCfg.Path), suggestion), 221 Subject: rng.ToHCL().Ptr(), 222 }) 223 return diags 224 } 225 226 providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr()) 227 schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerFqn, addr.Mode, addr.Type) 228 if err != nil { 229 // Prior validation should've taken care of a schema lookup error, 230 // so we should never get here but we'll handle it here anyway for 231 // robustness. 232 diags = diags.Append(&hcl.Diagnostic{ 233 Severity: hcl.DiagError, 234 Summary: `Failed provider schema lookup`, 235 Detail: fmt.Sprintf(`Couldn't load schema for %s resource type %q in %s: %s.`, modeAdjective, addr.Type, providerFqn.String(), err), 236 Subject: rng.ToHCL().Ptr(), 237 }) 238 } 239 240 if schema == nil { 241 // Prior validation should've taken care of a resource block with an 242 // unsupported type, so we should never get here but we'll handle it 243 // here anyway for robustness. 244 diags = diags.Append(&hcl.Diagnostic{ 245 Severity: hcl.DiagError, 246 Summary: `Invalid resource type`, 247 Detail: fmt.Sprintf(`A %s resource type %q is not supported by provider %q.`, modeAdjective, addr.Type, providerFqn.String()), 248 Subject: rng.ToHCL().Ptr(), 249 }) 250 return diags 251 } 252 253 // As a special case we'll detect attempts to access an attribute called 254 // "count" and produce a special error for it, since versions of Terraform 255 // prior to v0.12 offered this as a weird special case that we can no 256 // longer support. 257 if len(remain) > 0 { 258 if step, ok := remain[0].(hcl.TraverseAttr); ok && step.Name == "count" { 259 diags = diags.Append(&hcl.Diagnostic{ 260 Severity: hcl.DiagError, 261 Summary: `Invalid resource count attribute`, 262 Detail: fmt.Sprintf(`The special "count" attribute is no longer supported after Terraform v0.12. Instead, use length(%s) to count resource instances.`, addr), 263 Subject: rng.ToHCL().Ptr(), 264 }) 265 return diags 266 } 267 } 268 269 // If we got this far then we'll try to validate the remaining traversal 270 // steps against our schema. 271 moreDiags := schema.StaticValidateTraversal(remain) 272 diags = diags.Append(moreDiags) 273 274 return diags 275 } 276 277 func (d *evaluationStateData) staticValidateModuleCallReference(modCfg *configs.Config, addr addrs.ModuleCall, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics { 278 var diags tfdiags.Diagnostics 279 280 // For now, our focus here is just in testing that the referenced module 281 // call exists. All other validation is deferred until evaluation time. 282 _, exists := modCfg.Module.ModuleCalls[addr.Name] 283 if !exists { 284 var suggestions []string 285 for name := range modCfg.Module.ModuleCalls { 286 suggestions = append(suggestions, name) 287 } 288 sort.Strings(suggestions) 289 suggestion := didyoumean.NameSuggestion(addr.Name, suggestions) 290 if suggestion != "" { 291 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 292 } 293 294 diags = diags.Append(&hcl.Diagnostic{ 295 Severity: hcl.DiagError, 296 Summary: `Reference to undeclared module`, 297 Detail: fmt.Sprintf(`No module call named %q is declared in %s.%s`, addr.Name, moduleConfigDisplayAddr(modCfg.Path), suggestion), 298 Subject: rng.ToHCL().Ptr(), 299 }) 300 return diags 301 } 302 303 return diags 304 } 305 306 // moduleConfigDisplayAddr returns a string describing the given module 307 // address that is appropriate for returning to users in situations where the 308 // root module is possible. Specifically, it returns "the root module" if the 309 // root module instance is given, or a string representation of the module 310 // address otherwise. 311 func moduleConfigDisplayAddr(addr addrs.Module) string { 312 switch { 313 case addr.IsRoot(): 314 return "the root module" 315 default: 316 return addr.String() 317 } 318 }