github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/addrs/parse_target.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2/hclsyntax" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/terraform/tfdiags" 10 ) 11 12 // Target describes a targeted address with source location information. 13 type Target struct { 14 Subject Targetable 15 SourceRange tfdiags.SourceRange 16 } 17 18 // ParseTarget attempts to interpret the given traversal as a targetable 19 // address. The given traversal must be absolute, or this function will 20 // panic. 21 // 22 // If no error diagnostics are returned, the returned target includes the 23 // address that was extracted and the source range it was extracted from. 24 // 25 // If error diagnostics are returned then the Target value is invalid and 26 // must not be used. 27 func ParseTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) { 28 path, remain, diags := parseModuleInstancePrefix(traversal) 29 if diags.HasErrors() { 30 return nil, diags 31 } 32 33 rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange()) 34 35 if len(remain) == 0 { 36 return &Target{ 37 Subject: path, 38 SourceRange: rng, 39 }, diags 40 } 41 42 mode := ManagedResourceMode 43 if remain.RootName() == "data" { 44 mode = DataResourceMode 45 remain = remain[1:] 46 } 47 48 if len(remain) < 2 { 49 diags = diags.Append(&hcl.Diagnostic{ 50 Severity: hcl.DiagError, 51 Summary: "Invalid address", 52 Detail: "Resource specification must include a resource type and name.", 53 Subject: remain.SourceRange().Ptr(), 54 }) 55 return nil, diags 56 } 57 58 var typeName, name string 59 switch tt := remain[0].(type) { 60 case hcl.TraverseRoot: 61 typeName = tt.Name 62 case hcl.TraverseAttr: 63 typeName = tt.Name 64 default: 65 switch mode { 66 case ManagedResourceMode: 67 diags = diags.Append(&hcl.Diagnostic{ 68 Severity: hcl.DiagError, 69 Summary: "Invalid address", 70 Detail: "A resource type name is required.", 71 Subject: remain[0].SourceRange().Ptr(), 72 }) 73 case DataResourceMode: 74 diags = diags.Append(&hcl.Diagnostic{ 75 Severity: hcl.DiagError, 76 Summary: "Invalid address", 77 Detail: "A data source name is required.", 78 Subject: remain[0].SourceRange().Ptr(), 79 }) 80 default: 81 panic("unknown mode") 82 } 83 return nil, diags 84 } 85 86 switch tt := remain[1].(type) { 87 case hcl.TraverseAttr: 88 name = tt.Name 89 default: 90 diags = diags.Append(&hcl.Diagnostic{ 91 Severity: hcl.DiagError, 92 Summary: "Invalid address", 93 Detail: "A resource name is required.", 94 Subject: remain[1].SourceRange().Ptr(), 95 }) 96 return nil, diags 97 } 98 99 var subject Targetable 100 remain = remain[2:] 101 switch len(remain) { 102 case 0: 103 subject = path.Resource(mode, typeName, name) 104 case 1: 105 if tt, ok := remain[0].(hcl.TraverseIndex); ok { 106 key, err := ParseInstanceKey(tt.Key) 107 if err != nil { 108 diags = diags.Append(&hcl.Diagnostic{ 109 Severity: hcl.DiagError, 110 Summary: "Invalid address", 111 Detail: fmt.Sprintf("Invalid resource instance key: %s.", err), 112 Subject: remain[0].SourceRange().Ptr(), 113 }) 114 return nil, diags 115 } 116 117 subject = path.ResourceInstance(mode, typeName, name, key) 118 } else { 119 diags = diags.Append(&hcl.Diagnostic{ 120 Severity: hcl.DiagError, 121 Summary: "Invalid address", 122 Detail: "Resource instance key must be given in square brackets.", 123 Subject: remain[0].SourceRange().Ptr(), 124 }) 125 return nil, diags 126 } 127 default: 128 diags = diags.Append(&hcl.Diagnostic{ 129 Severity: hcl.DiagError, 130 Summary: "Invalid address", 131 Detail: "Unexpected extra operators after address.", 132 Subject: remain[1].SourceRange().Ptr(), 133 }) 134 return nil, diags 135 } 136 137 return &Target{ 138 Subject: subject, 139 SourceRange: rng, 140 }, diags 141 } 142 143 // ParseTargetStr is a helper wrapper around ParseTarget that takes a string 144 // and parses it with the HCL native syntax traversal parser before 145 // interpreting it. 146 // 147 // This should be used only in specialized situations since it will cause the 148 // created references to not have any meaningful source location information. 149 // If a target string is coming from a source that should be identified in 150 // error messages then the caller should instead parse it directly using a 151 // suitable function from the HCL API and pass the traversal itself to 152 // ParseTarget. 153 // 154 // Error diagnostics are returned if either the parsing fails or the analysis 155 // of the traversal fails. There is no way for the caller to distinguish the 156 // two kinds of diagnostics programmatically. If error diagnostics are returned 157 // the returned target may be nil or incomplete. 158 func ParseTargetStr(str string) (*Target, tfdiags.Diagnostics) { 159 var diags tfdiags.Diagnostics 160 161 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 162 diags = diags.Append(parseDiags) 163 if parseDiags.HasErrors() { 164 return nil, diags 165 } 166 167 target, targetDiags := ParseTarget(traversal) 168 diags = diags.Append(targetDiags) 169 return target, diags 170 } 171 172 // ParseAbsResource attempts to interpret the given traversal as an absolute 173 // resource address, using the same syntax as expected by ParseTarget. 174 // 175 // If no error diagnostics are returned, the returned target includes the 176 // address that was extracted and the source range it was extracted from. 177 // 178 // If error diagnostics are returned then the AbsResource value is invalid and 179 // must not be used. 180 func ParseAbsResource(traversal hcl.Traversal) (AbsResource, tfdiags.Diagnostics) { 181 addr, diags := ParseTarget(traversal) 182 if diags.HasErrors() { 183 return AbsResource{}, diags 184 } 185 186 switch tt := addr.Subject.(type) { 187 188 case AbsResource: 189 return tt, diags 190 191 case AbsResourceInstance: // Catch likely user error with specialized message 192 // Assume that the last element of the traversal must be the index, 193 // since that's required for a valid resource instance address. 194 indexStep := traversal[len(traversal)-1] 195 diags = diags.Append(&hcl.Diagnostic{ 196 Severity: hcl.DiagError, 197 Summary: "Invalid address", 198 Detail: "A resource address is required. This instance key identifies a specific resource instance, which is not expected here.", 199 Subject: indexStep.SourceRange().Ptr(), 200 }) 201 return AbsResource{}, diags 202 203 case ModuleInstance: // Catch likely user error with specialized message 204 diags = diags.Append(&hcl.Diagnostic{ 205 Severity: hcl.DiagError, 206 Summary: "Invalid address", 207 Detail: "A resource address is required here. The module path must be followed by a resource specification.", 208 Subject: traversal.SourceRange().Ptr(), 209 }) 210 return AbsResource{}, diags 211 212 default: // Generic message for other address types 213 diags = diags.Append(&hcl.Diagnostic{ 214 Severity: hcl.DiagError, 215 Summary: "Invalid address", 216 Detail: "A resource address is required here.", 217 Subject: traversal.SourceRange().Ptr(), 218 }) 219 return AbsResource{}, diags 220 221 } 222 } 223 224 // ParseAbsResourceStr is a helper wrapper around ParseAbsResource that takes a 225 // string and parses it with the HCL native syntax traversal parser before 226 // interpreting it. 227 // 228 // Error diagnostics are returned if either the parsing fails or the analysis 229 // of the traversal fails. There is no way for the caller to distinguish the 230 // two kinds of diagnostics programmatically. If error diagnostics are returned 231 // the returned address may be incomplete. 232 // 233 // Since this function has no context about the source of the given string, 234 // any returned diagnostics will not have meaningful source location 235 // information. 236 func ParseAbsResourceStr(str string) (AbsResource, tfdiags.Diagnostics) { 237 var diags tfdiags.Diagnostics 238 239 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 240 diags = diags.Append(parseDiags) 241 if parseDiags.HasErrors() { 242 return AbsResource{}, diags 243 } 244 245 addr, addrDiags := ParseAbsResource(traversal) 246 diags = diags.Append(addrDiags) 247 return addr, diags 248 } 249 250 // ParseAbsResourceInstance attempts to interpret the given traversal as an 251 // absolute resource instance address, using the same syntax as expected by 252 // ParseTarget. 253 // 254 // If no error diagnostics are returned, the returned target includes the 255 // address that was extracted and the source range it was extracted from. 256 // 257 // If error diagnostics are returned then the AbsResource value is invalid and 258 // must not be used. 259 func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) { 260 addr, diags := ParseTarget(traversal) 261 if diags.HasErrors() { 262 return AbsResourceInstance{}, diags 263 } 264 265 switch tt := addr.Subject.(type) { 266 267 case AbsResource: 268 return tt.Instance(NoKey), diags 269 270 case AbsResourceInstance: 271 return tt, diags 272 273 case ModuleInstance: // Catch likely user error with specialized message 274 diags = diags.Append(&hcl.Diagnostic{ 275 Severity: hcl.DiagError, 276 Summary: "Invalid address", 277 Detail: "A resource instance address is required here. The module path must be followed by a resource instance specification.", 278 Subject: traversal.SourceRange().Ptr(), 279 }) 280 return AbsResourceInstance{}, diags 281 282 default: // Generic message for other address types 283 diags = diags.Append(&hcl.Diagnostic{ 284 Severity: hcl.DiagError, 285 Summary: "Invalid address", 286 Detail: "A resource address is required here.", 287 Subject: traversal.SourceRange().Ptr(), 288 }) 289 return AbsResourceInstance{}, diags 290 291 } 292 } 293 294 // ParseAbsResourceInstanceStr is a helper wrapper around 295 // ParseAbsResourceInstance that takes a string and parses it with the HCL 296 // native syntax traversal parser before interpreting it. 297 // 298 // Error diagnostics are returned if either the parsing fails or the analysis 299 // of the traversal fails. There is no way for the caller to distinguish the 300 // two kinds of diagnostics programmatically. If error diagnostics are returned 301 // the returned address may be incomplete. 302 // 303 // Since this function has no context about the source of the given string, 304 // any returned diagnostics will not have meaningful source location 305 // information. 306 func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) { 307 var diags tfdiags.Diagnostics 308 309 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 310 diags = diags.Append(parseDiags) 311 if parseDiags.HasErrors() { 312 return AbsResourceInstance{}, diags 313 } 314 315 addr, addrDiags := ParseAbsResourceInstance(traversal) 316 diags = diags.Append(addrDiags) 317 return addr, diags 318 }