github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/terraform/addrs/parse_ref.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 ) 8 9 // Reference describes a reference to an address with source location 10 // information. 11 type Reference struct { 12 Subject Referenceable 13 SourceRange hcl.Range 14 Remaining hcl.Traversal 15 } 16 17 // ParseRef attempts to extract a referencable address from the prefix of the 18 // given traversal, which must be an absolute traversal or this function 19 // will panic. 20 // 21 // If no error diagnostics are returned, the returned reference includes the 22 // address that was extracted, the source range it was extracted from, and any 23 // remaining relative traversal that was not consumed as part of the 24 // reference. 25 // 26 // If error diagnostics are returned then the Reference value is invalid and 27 // must not be used. 28 func ParseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) { 29 ref, diags := parseRef(traversal) 30 31 // Normalize a little to make life easier for callers. 32 if ref != nil { 33 if len(ref.Remaining) == 0 { 34 ref.Remaining = nil 35 } 36 } 37 38 return ref, diags 39 } 40 41 func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) { 42 var diags hcl.Diagnostics 43 44 root := traversal.RootName() 45 rootRange := traversal[0].SourceRange() 46 47 switch root { 48 49 case "count": 50 name, rng, remain, diags := parseSingleAttrRef(traversal) 51 return &Reference{ 52 Subject: CountAttr{Name: name}, 53 SourceRange: rng, 54 Remaining: remain, 55 }, diags 56 57 case "each": 58 name, rng, remain, diags := parseSingleAttrRef(traversal) 59 return &Reference{ 60 Subject: ForEachAttr{Name: name}, 61 SourceRange: rng, 62 Remaining: remain, 63 }, diags 64 65 case "data": 66 if len(traversal) < 3 { 67 diags = diags.Append(&hcl.Diagnostic{ 68 Severity: hcl.DiagError, 69 Summary: "Invalid reference", 70 Detail: `The "data" object must be followed by two attribute names: the data source type and the resource name.`, 71 Subject: traversal.SourceRange().Ptr(), 72 }) 73 return nil, diags 74 } 75 remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser 76 return parseResourceRef(DataResourceMode, rootRange, remain) 77 78 case "resource": 79 // This is an alias for the normal case of just using a managed resource 80 // type as a top-level symbol, which will serve as an escape mechanism 81 // if a later edition of the Terraform language introduces a new 82 // reference prefix that conflicts with a resource type name in an 83 // existing provider. In that case, the edition upgrade tool can 84 // rewrite foo.bar into resource.foo.bar to ensure that "foo" remains 85 // interpreted as a resource type name rather than as the new reserved 86 // word. 87 if len(traversal) < 3 { 88 diags = diags.Append(&hcl.Diagnostic{ 89 Severity: hcl.DiagError, 90 Summary: "Invalid reference", 91 Detail: `The "resource" object must be followed by two attribute names: the resource type and the resource name.`, 92 Subject: traversal.SourceRange().Ptr(), 93 }) 94 return nil, diags 95 } 96 remain := traversal[1:] // trim off "resource" so we can use our shared resource reference parser 97 return parseResourceRef(ManagedResourceMode, rootRange, remain) 98 99 case "local": 100 name, rng, remain, diags := parseSingleAttrRef(traversal) 101 return &Reference{ 102 Subject: LocalValue{Name: name}, 103 SourceRange: rng, 104 Remaining: remain, 105 }, diags 106 107 case "module": 108 callName, callRange, remain, diags := parseSingleAttrRef(traversal) 109 if diags.HasErrors() { 110 return nil, diags 111 } 112 113 // A traversal starting with "module" can either be a reference to an 114 // entire module, or to a single output from a module instance, 115 // depending on what we find after this introducer. 116 callInstance := ModuleCallInstance{ 117 Call: ModuleCall{ 118 Name: callName, 119 }, 120 Key: NoKey, 121 } 122 123 if len(remain) == 0 { 124 // Reference to an entire module. Might alternatively be a 125 // reference to a single instance of a particular module, but the 126 // caller will need to deal with that ambiguity since we don't have 127 // enough context here. 128 return &Reference{ 129 Subject: callInstance.Call, 130 SourceRange: callRange, 131 Remaining: remain, 132 }, diags 133 } 134 135 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok { 136 var err error 137 callInstance.Key, err = ParseInstanceKey(idxTrav.Key) 138 if err != nil { 139 diags = diags.Append(&hcl.Diagnostic{ 140 Severity: hcl.DiagError, 141 Summary: "Invalid index key", 142 Detail: fmt.Sprintf("Invalid index for module instance: %s.", err), 143 Subject: &idxTrav.SrcRange, 144 }) 145 return nil, diags 146 } 147 remain = remain[1:] 148 149 if len(remain) == 0 { 150 // Also a reference to an entire module instance, but we have a key 151 // now. 152 return &Reference{ 153 Subject: callInstance, 154 SourceRange: hcl.RangeBetween(callRange, idxTrav.SrcRange), 155 Remaining: remain, 156 }, diags 157 } 158 } 159 160 if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok { 161 remain = remain[1:] 162 return &Reference{ 163 Subject: ModuleCallInstanceOutput{ 164 Name: attrTrav.Name, 165 Call: callInstance, 166 }, 167 SourceRange: hcl.RangeBetween(callRange, attrTrav.SrcRange), 168 Remaining: remain, 169 }, diags 170 } 171 172 diags = diags.Append(&hcl.Diagnostic{ 173 Severity: hcl.DiagError, 174 Summary: "Invalid reference", 175 Detail: "Module instance objects do not support this operation.", 176 Subject: remain[0].SourceRange().Ptr(), 177 }) 178 return nil, diags 179 180 case "path": 181 name, rng, remain, diags := parseSingleAttrRef(traversal) 182 return &Reference{ 183 Subject: PathAttr{Name: name}, 184 SourceRange: rng, 185 Remaining: remain, 186 }, diags 187 188 case "self": 189 return &Reference{ 190 Subject: Self, 191 SourceRange: rootRange, 192 Remaining: traversal[1:], 193 }, diags 194 195 case "terraform": 196 name, rng, remain, diags := parseSingleAttrRef(traversal) 197 return &Reference{ 198 Subject: TerraformAttr{Name: name}, 199 SourceRange: rng, 200 Remaining: remain, 201 }, diags 202 203 case "var": 204 name, rng, remain, diags := parseSingleAttrRef(traversal) 205 return &Reference{ 206 Subject: InputVariable{Name: name}, 207 SourceRange: rng, 208 Remaining: remain, 209 }, diags 210 211 case "template", "lazy", "arg": 212 // These names are all pre-emptively reserved in the hope of landing 213 // some version of "template values" or "lazy expressions" feature 214 // before the next opt-in language edition, but don't yet do anything. 215 diags = diags.Append(&hcl.Diagnostic{ 216 Severity: hcl.DiagError, 217 Summary: "Reserved symbol name", 218 Detail: fmt.Sprintf("The symbol name %q is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix \"resource.\" to force interpretation as a resource type name.", root), 219 Subject: rootRange.Ptr(), 220 }) 221 return nil, diags 222 223 default: 224 return parseResourceRef(ManagedResourceMode, rootRange, traversal) 225 } 226 } 227 228 func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, hcl.Diagnostics) { 229 var diags hcl.Diagnostics 230 231 if len(traversal) < 2 { 232 diags = diags.Append(&hcl.Diagnostic{ 233 Severity: hcl.DiagError, 234 Summary: "Invalid reference", 235 Detail: `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`, 236 Subject: hcl.RangeBetween(traversal[0].SourceRange(), traversal[len(traversal)-1].SourceRange()).Ptr(), 237 }) 238 return nil, diags 239 } 240 241 var typeName, name string 242 switch tt := traversal[0].(type) { // Could be either root or attr, depending on our resource mode 243 case hcl.TraverseRoot: 244 typeName = tt.Name 245 case hcl.TraverseAttr: 246 typeName = tt.Name 247 default: 248 // If it isn't a TraverseRoot then it must be a "data" reference. 249 diags = diags.Append(&hcl.Diagnostic{ 250 Severity: hcl.DiagError, 251 Summary: "Invalid reference", 252 Detail: `The "data" object does not support this operation.`, 253 Subject: traversal[0].SourceRange().Ptr(), 254 }) 255 return nil, diags 256 } 257 258 attrTrav, ok := traversal[1].(hcl.TraverseAttr) 259 if !ok { 260 var what string 261 switch mode { 262 case DataResourceMode: 263 what = "data source" 264 default: 265 what = "resource type" 266 } 267 diags = diags.Append(&hcl.Diagnostic{ 268 Severity: hcl.DiagError, 269 Summary: "Invalid reference", 270 Detail: fmt.Sprintf(`A reference to a %s must be followed by at least one attribute access, specifying the resource name.`, what), 271 Subject: traversal[1].SourceRange().Ptr(), 272 }) 273 return nil, diags 274 } 275 name = attrTrav.Name 276 rng := hcl.RangeBetween(startRange, attrTrav.SrcRange) 277 remain := traversal[2:] 278 279 resourceAddr := Resource{ 280 Mode: mode, 281 Type: typeName, 282 Name: name, 283 } 284 resourceInstAddr := ResourceInstance{ 285 Resource: resourceAddr, 286 Key: NoKey, 287 } 288 289 if len(remain) == 0 { 290 // This might actually be a reference to the collection of all instances 291 // of the resource, but we don't have enough context here to decide 292 // so we'll let the caller resolve that ambiguity. 293 return &Reference{ 294 Subject: resourceAddr, 295 SourceRange: rng, 296 }, diags 297 } 298 299 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok { 300 var err error 301 resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key) 302 if err != nil { 303 diags = diags.Append(&hcl.Diagnostic{ 304 Severity: hcl.DiagError, 305 Summary: "Invalid index key", 306 Detail: fmt.Sprintf("Invalid index for resource instance: %s.", err), 307 Subject: &idxTrav.SrcRange, 308 }) 309 return nil, diags 310 } 311 remain = remain[1:] 312 rng = hcl.RangeBetween(rng, idxTrav.SrcRange) 313 } 314 315 return &Reference{ 316 Subject: resourceInstAddr, 317 SourceRange: rng, 318 Remaining: remain, 319 }, diags 320 } 321 322 func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, hcl.Diagnostics) { 323 var diags hcl.Diagnostics 324 325 root := traversal.RootName() 326 rootRange := traversal[0].SourceRange() 327 328 if len(traversal) < 2 { 329 diags = diags.Append(&hcl.Diagnostic{ 330 Severity: hcl.DiagError, 331 Summary: "Invalid reference", 332 Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root), 333 Subject: &rootRange, 334 }) 335 return "", hcl.Range{}, nil, diags 336 } 337 if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok { 338 return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags 339 } 340 diags = diags.Append(&hcl.Diagnostic{ 341 Severity: hcl.DiagError, 342 Summary: "Invalid reference", 343 Detail: fmt.Sprintf("The %q object does not support this operation.", root), 344 Subject: traversal[1].SourceRange().Ptr(), 345 }) 346 return "", hcl.Range{}, nil, diags 347 }