github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/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-plugin-sdk/internal/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 // ParseAbsResourceInstance attempts to interpret the given traversal as an 173 // absolute resource instance address, using the same syntax as expected by 174 // ParseTarget. 175 // 176 // If no error diagnostics are returned, the returned target includes the 177 // address that was extracted and the source range it was extracted from. 178 // 179 // If error diagnostics are returned then the AbsResource value is invalid and 180 // must not be used. 181 func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) { 182 addr, diags := ParseTarget(traversal) 183 if diags.HasErrors() { 184 return AbsResourceInstance{}, diags 185 } 186 187 switch tt := addr.Subject.(type) { 188 189 case AbsResource: 190 return tt.Instance(NoKey), diags 191 192 case AbsResourceInstance: 193 return tt, diags 194 195 case ModuleInstance: // Catch likely user error with specialized message 196 diags = diags.Append(&hcl.Diagnostic{ 197 Severity: hcl.DiagError, 198 Summary: "Invalid address", 199 Detail: "A resource instance address is required here. The module path must be followed by a resource instance specification.", 200 Subject: traversal.SourceRange().Ptr(), 201 }) 202 return AbsResourceInstance{}, diags 203 204 default: // Generic message for other address types 205 diags = diags.Append(&hcl.Diagnostic{ 206 Severity: hcl.DiagError, 207 Summary: "Invalid address", 208 Detail: "A resource address is required here.", 209 Subject: traversal.SourceRange().Ptr(), 210 }) 211 return AbsResourceInstance{}, diags 212 213 } 214 } 215 216 // ParseAbsResourceInstanceStr is a helper wrapper around 217 // ParseAbsResourceInstance that takes a string and parses it with the HCL 218 // native syntax traversal parser before interpreting it. 219 // 220 // Error diagnostics are returned if either the parsing fails or the analysis 221 // of the traversal fails. There is no way for the caller to distinguish the 222 // two kinds of diagnostics programmatically. If error diagnostics are returned 223 // the returned address may be incomplete. 224 // 225 // Since this function has no context about the source of the given string, 226 // any returned diagnostics will not have meaningful source location 227 // information. 228 func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) { 229 var diags tfdiags.Diagnostics 230 231 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 232 diags = diags.Append(parseDiags) 233 if parseDiags.HasErrors() { 234 return AbsResourceInstance{}, diags 235 } 236 237 addr, addrDiags := ParseAbsResourceInstance(traversal) 238 diags = diags.Append(addrDiags) 239 return addr, diags 240 }