github.com/pulumi/terraform@v1.4.0/pkg/addrs/module_instance.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 "github.com/zclconf/go-cty/cty" 10 "github.com/zclconf/go-cty/cty/gocty" 11 12 "github.com/pulumi/terraform/pkg/tfdiags" 13 ) 14 15 // ModuleInstance is an address for a particular module instance within the 16 // dynamic module tree. This is an extension of the static traversals 17 // represented by type Module that deals with the possibility of a single 18 // module call producing multiple instances via the "count" and "for_each" 19 // arguments. 20 // 21 // Although ModuleInstance is a slice, it should be treated as immutable after 22 // creation. 23 type ModuleInstance []ModuleInstanceStep 24 25 var ( 26 _ Targetable = ModuleInstance(nil) 27 ) 28 29 func ParseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) { 30 mi, remain, diags := parseModuleInstancePrefix(traversal) 31 if len(remain) != 0 { 32 if len(remain) == len(traversal) { 33 diags = diags.Append(&hcl.Diagnostic{ 34 Severity: hcl.DiagError, 35 Summary: "Invalid module instance address", 36 Detail: "A module instance address must begin with \"module.\".", 37 Subject: remain.SourceRange().Ptr(), 38 }) 39 } else { 40 diags = diags.Append(&hcl.Diagnostic{ 41 Severity: hcl.DiagError, 42 Summary: "Invalid module instance address", 43 Detail: "The module instance address is followed by additional invalid content.", 44 Subject: remain.SourceRange().Ptr(), 45 }) 46 } 47 } 48 return mi, diags 49 } 50 51 // ParseModuleInstanceStr is a helper wrapper around ParseModuleInstance 52 // that takes a string and parses it with the HCL native syntax traversal parser 53 // before interpreting it. 54 // 55 // This should be used only in specialized situations since it will cause the 56 // created references to not have any meaningful source location information. 57 // If a reference string is coming from a source that should be identified in 58 // error messages then the caller should instead parse it directly using a 59 // suitable function from the HCL API and pass the traversal itself to 60 // ParseModuleInstance. 61 // 62 // Error diagnostics are returned if either the parsing fails or the analysis 63 // of the traversal fails. There is no way for the caller to distinguish the 64 // two kinds of diagnostics programmatically. If error diagnostics are returned 65 // then the returned address is invalid. 66 func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) { 67 var diags tfdiags.Diagnostics 68 69 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 70 diags = diags.Append(parseDiags) 71 if parseDiags.HasErrors() { 72 return nil, diags 73 } 74 75 addr, addrDiags := ParseModuleInstance(traversal) 76 diags = diags.Append(addrDiags) 77 return addr, diags 78 } 79 80 func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) { 81 remain := traversal 82 var mi ModuleInstance 83 var diags tfdiags.Diagnostics 84 85 LOOP: 86 for len(remain) > 0 { 87 var next string 88 switch tt := remain[0].(type) { 89 case hcl.TraverseRoot: 90 next = tt.Name 91 case hcl.TraverseAttr: 92 next = tt.Name 93 default: 94 diags = diags.Append(&hcl.Diagnostic{ 95 Severity: hcl.DiagError, 96 Summary: "Invalid address operator", 97 Detail: "Module address prefix must be followed by dot and then a name.", 98 Subject: remain[0].SourceRange().Ptr(), 99 }) 100 break LOOP 101 } 102 103 if next != "module" { 104 break 105 } 106 107 kwRange := remain[0].SourceRange() 108 remain = remain[1:] 109 // If we have the prefix "module" then we should be followed by an 110 // module call name, as an attribute, and then optionally an index step 111 // giving the instance key. 112 if len(remain) == 0 { 113 diags = diags.Append(&hcl.Diagnostic{ 114 Severity: hcl.DiagError, 115 Summary: "Invalid address operator", 116 Detail: "Prefix \"module.\" must be followed by a module name.", 117 Subject: &kwRange, 118 }) 119 break 120 } 121 122 var moduleName string 123 switch tt := remain[0].(type) { 124 case hcl.TraverseAttr: 125 moduleName = tt.Name 126 default: 127 diags = diags.Append(&hcl.Diagnostic{ 128 Severity: hcl.DiagError, 129 Summary: "Invalid address operator", 130 Detail: "Prefix \"module.\" must be followed by a module name.", 131 Subject: remain[0].SourceRange().Ptr(), 132 }) 133 break LOOP 134 } 135 remain = remain[1:] 136 step := ModuleInstanceStep{ 137 Name: moduleName, 138 } 139 140 if len(remain) > 0 { 141 if idx, ok := remain[0].(hcl.TraverseIndex); ok { 142 remain = remain[1:] 143 144 switch idx.Key.Type() { 145 case cty.String: 146 step.InstanceKey = StringKey(idx.Key.AsString()) 147 case cty.Number: 148 var idxInt int 149 err := gocty.FromCtyValue(idx.Key, &idxInt) 150 if err == nil { 151 step.InstanceKey = IntKey(idxInt) 152 } else { 153 diags = diags.Append(&hcl.Diagnostic{ 154 Severity: hcl.DiagError, 155 Summary: "Invalid address operator", 156 Detail: fmt.Sprintf("Invalid module index: %s.", err), 157 Subject: idx.SourceRange().Ptr(), 158 }) 159 } 160 default: 161 // Should never happen, because no other types are allowed in traversal indices. 162 diags = diags.Append(&hcl.Diagnostic{ 163 Severity: hcl.DiagError, 164 Summary: "Invalid address operator", 165 Detail: "Invalid module key: must be either a string or an integer.", 166 Subject: idx.SourceRange().Ptr(), 167 }) 168 } 169 } 170 } 171 172 mi = append(mi, step) 173 } 174 175 var retRemain hcl.Traversal 176 if len(remain) > 0 { 177 retRemain = make(hcl.Traversal, len(remain)) 178 copy(retRemain, remain) 179 // The first element here might be either a TraverseRoot or a 180 // TraverseAttr, depending on whether we had a module address on the 181 // front. To make life easier for callers, we'll normalize to always 182 // start with a TraverseRoot. 183 if tt, ok := retRemain[0].(hcl.TraverseAttr); ok { 184 retRemain[0] = hcl.TraverseRoot{ 185 Name: tt.Name, 186 SrcRange: tt.SrcRange, 187 } 188 } 189 } 190 191 return mi, retRemain, diags 192 } 193 194 // UnkeyedInstanceShim is a shim method for converting a Module address to the 195 // equivalent ModuleInstance address that assumes that no modules have 196 // keyed instances. 197 // 198 // This is a temporary allowance for the fact that Terraform does not presently 199 // support "count" and "for_each" on modules, and thus graph building code that 200 // derives graph nodes from configuration must just assume unkeyed modules 201 // in order to construct the graph. At a later time when "count" and "for_each" 202 // support is added for modules, all callers of this method will need to be 203 // reworked to allow for keyed module instances. 204 func (m Module) UnkeyedInstanceShim() ModuleInstance { 205 path := make(ModuleInstance, len(m)) 206 for i, name := range m { 207 path[i] = ModuleInstanceStep{Name: name} 208 } 209 return path 210 } 211 212 // ModuleInstanceStep is a single traversal step through the dynamic module 213 // tree. It is used only as part of ModuleInstance. 214 type ModuleInstanceStep struct { 215 Name string 216 InstanceKey InstanceKey 217 } 218 219 // RootModuleInstance is the module instance address representing the root 220 // module, which is also the zero value of ModuleInstance. 221 var RootModuleInstance ModuleInstance 222 223 // IsRoot returns true if the receiver is the address of the root module instance, 224 // or false otherwise. 225 func (m ModuleInstance) IsRoot() bool { 226 return len(m) == 0 227 } 228 229 // Child returns the address of a child module instance of the receiver, 230 // identified by the given name and key. 231 func (m ModuleInstance) Child(name string, key InstanceKey) ModuleInstance { 232 ret := make(ModuleInstance, 0, len(m)+1) 233 ret = append(ret, m...) 234 return append(ret, ModuleInstanceStep{ 235 Name: name, 236 InstanceKey: key, 237 }) 238 } 239 240 // ChildCall returns the address of a module call within the receiver, 241 // identified by the given name. 242 func (m ModuleInstance) ChildCall(name string) AbsModuleCall { 243 return AbsModuleCall{ 244 Module: m, 245 Call: ModuleCall{Name: name}, 246 } 247 } 248 249 // Parent returns the address of the parent module instance of the receiver, or 250 // the receiver itself if there is no parent (if it's the root module address). 251 func (m ModuleInstance) Parent() ModuleInstance { 252 if len(m) == 0 { 253 return m 254 } 255 return m[:len(m)-1] 256 } 257 258 // String returns a string representation of the receiver, in the format used 259 // within e.g. user-provided resource addresses. 260 // 261 // The address of the root module has the empty string as its representation. 262 func (m ModuleInstance) String() string { 263 if len(m) == 0 { 264 return "" 265 } 266 // Calculate minimal necessary space (no instance keys). 267 l := 0 268 for _, step := range m { 269 l += len(step.Name) 270 } 271 buf := strings.Builder{} 272 // 8 is len(".module.") which separates entries. 273 buf.Grow(l + len(m)*8) 274 sep := "" 275 for _, step := range m { 276 buf.WriteString(sep) 277 buf.WriteString("module.") 278 buf.WriteString(step.Name) 279 if step.InstanceKey != NoKey { 280 buf.WriteString(step.InstanceKey.String()) 281 } 282 sep = "." 283 } 284 return buf.String() 285 } 286 287 type moduleInstanceKey string 288 289 func (m ModuleInstance) UniqueKey() UniqueKey { 290 return moduleInstanceKey(m.String()) 291 } 292 293 func (mk moduleInstanceKey) uniqueKeySigil() {} 294 295 // Equal returns true if the receiver and the given other value 296 // contains the exact same parts. 297 func (m ModuleInstance) Equal(o ModuleInstance) bool { 298 if len(m) != len(o) { 299 return false 300 } 301 302 for i := range m { 303 if m[i] != o[i] { 304 return false 305 } 306 } 307 return true 308 } 309 310 // Less returns true if the receiver should sort before the given other value 311 // in a sorted list of addresses. 312 func (m ModuleInstance) Less(o ModuleInstance) bool { 313 if len(m) != len(o) { 314 // Shorter path sorts first. 315 return len(m) < len(o) 316 } 317 318 for i := range m { 319 mS, oS := m[i], o[i] 320 switch { 321 case mS.Name != oS.Name: 322 return mS.Name < oS.Name 323 case mS.InstanceKey != oS.InstanceKey: 324 return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey) 325 } 326 } 327 328 return false 329 } 330 331 // Ancestors returns a slice containing the receiver and all of its ancestor 332 // module instances, all the way up to (and including) the root module. 333 // The result is ordered by depth, with the root module always first. 334 // 335 // Since the result always includes the root module, a caller may choose to 336 // ignore it by slicing the result with [1:]. 337 func (m ModuleInstance) Ancestors() []ModuleInstance { 338 ret := make([]ModuleInstance, 0, len(m)+1) 339 for i := 0; i <= len(m); i++ { 340 ret = append(ret, m[:i]) 341 } 342 return ret 343 } 344 345 // IsAncestor returns true if the receiver is an ancestor of the given 346 // other value. 347 func (m ModuleInstance) IsAncestor(o ModuleInstance) bool { 348 // Longer or equal sized paths means the receiver cannot 349 // be an ancestor of the given module insatnce. 350 if len(m) >= len(o) { 351 return false 352 } 353 354 for i, ms := range m { 355 if ms.Name != o[i].Name { 356 return false 357 } 358 if ms.InstanceKey != NoKey && ms.InstanceKey != o[i].InstanceKey { 359 return false 360 } 361 } 362 363 return true 364 } 365 366 // Call returns the module call address that corresponds to the given module 367 // instance, along with the address of the module instance that contains it. 368 // 369 // There is no call for the root module, so this method will panic if called 370 // on the root module address. 371 // 372 // A single module call can produce potentially many module instances, so the 373 // result discards any instance key that might be present on the last step 374 // of the instance. To retain this, use CallInstance instead. 375 // 376 // In practice, this just turns the last element of the receiver into a 377 // ModuleCall and then returns a slice of the receiever that excludes that 378 // last part. This is just a convenience for situations where a call address 379 // is required, such as when dealing with *Reference and Referencable values. 380 func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) { 381 if len(m) == 0 { 382 panic("cannot produce ModuleCall for root module") 383 } 384 385 inst, lastStep := m[:len(m)-1], m[len(m)-1] 386 return inst, ModuleCall{ 387 Name: lastStep.Name, 388 } 389 } 390 391 // CallInstance returns the module call instance address that corresponds to 392 // the given module instance, along with the address of the module instance 393 // that contains it. 394 // 395 // There is no call for the root module, so this method will panic if called 396 // on the root module address. 397 // 398 // In practice, this just turns the last element of the receiver into a 399 // ModuleCallInstance and then returns a slice of the receiever that excludes 400 // that last part. This is just a convenience for situations where a call\ 401 // address is required, such as when dealing with *Reference and Referencable 402 // values. 403 func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) { 404 if len(m) == 0 { 405 panic("cannot produce ModuleCallInstance for root module") 406 } 407 408 inst, lastStep := m[:len(m)-1], m[len(m)-1] 409 return inst, ModuleCallInstance{ 410 Call: ModuleCall{ 411 Name: lastStep.Name, 412 }, 413 Key: lastStep.InstanceKey, 414 } 415 } 416 417 // TargetContains implements Targetable by returning true if the given other 418 // address either matches the receiver, is a sub-module-instance of the 419 // receiver, or is a targetable absolute address within a module that 420 // is contained within the reciever. 421 func (m ModuleInstance) TargetContains(other Targetable) bool { 422 switch to := other.(type) { 423 case Module: 424 if len(to) < len(m) { 425 // Can't be contained if the path is shorter 426 return false 427 } 428 // Other is contained if its steps match for the length of our own path. 429 for i, ourStep := range m { 430 otherStep := to[i] 431 432 // We can't contain an entire module if we have a specific instance 433 // key. The case of NoKey is OK because this address is either 434 // meant to address an unexpanded module, or a single instance of 435 // that module, and both of those are a covered in-full by the 436 // Module address. 437 if ourStep.InstanceKey != NoKey { 438 return false 439 } 440 441 if ourStep.Name != otherStep { 442 return false 443 } 444 } 445 // If we fall out here then the prefixed matched, so it's contained. 446 return true 447 448 case ModuleInstance: 449 if len(to) < len(m) { 450 return false 451 } 452 for i, ourStep := range m { 453 otherStep := to[i] 454 455 if ourStep.Name != otherStep.Name { 456 return false 457 } 458 459 // if this is our last step, because all targets are parsed as 460 // instances, this may be a ModuleInstance intended to be used as a 461 // Module. 462 if i == len(m)-1 { 463 if ourStep.InstanceKey == NoKey { 464 // If the other step is a keyed instance, then we contain that 465 // step, and if it isn't it's a match, which is true either way 466 return true 467 } 468 } 469 470 if ourStep.InstanceKey != otherStep.InstanceKey { 471 return false 472 } 473 474 } 475 return true 476 477 case ConfigResource: 478 return m.TargetContains(to.Module) 479 480 case AbsResource: 481 return m.TargetContains(to.Module) 482 483 case AbsResourceInstance: 484 return m.TargetContains(to.Module) 485 486 default: 487 return false 488 } 489 } 490 491 // Module returns the address of the module that this instance is an instance 492 // of. 493 func (m ModuleInstance) Module() Module { 494 if len(m) == 0 { 495 return nil 496 } 497 ret := make(Module, len(m)) 498 for i, step := range m { 499 ret[i] = step.Name 500 } 501 return ret 502 } 503 504 func (m ModuleInstance) AddrType() TargetableAddrType { 505 return ModuleInstanceAddrType 506 } 507 508 func (m ModuleInstance) targetableSigil() { 509 // ModuleInstance is targetable 510 } 511 512 func (m ModuleInstance) absMoveableSigil() { 513 // ModuleInstance is moveable 514 } 515 516 // IsDeclaredByCall returns true if the receiver is an instance of the given 517 // AbsModuleCall. 518 func (m ModuleInstance) IsDeclaredByCall(other AbsModuleCall) bool { 519 // Compare len(m) to len(other.Module+1) because the final module instance 520 // step in other is stored in the AbsModuleCall.Call 521 if len(m) > len(other.Module)+1 || len(m) == 0 && len(other.Module) == 0 { 522 return false 523 } 524 525 // Verify that the other's ModuleInstance matches the receiver. 526 inst, lastStep := other.Module, other.Call 527 for i := range inst { 528 if inst[i] != m[i] { 529 return false 530 } 531 } 532 533 // Now compare the final step of the received with the other Call, where 534 // only the name needs to match. 535 return lastStep.Name == m[len(m)-1].Name 536 } 537 538 func (s ModuleInstanceStep) String() string { 539 if s.InstanceKey != NoKey { 540 return s.Name + s.InstanceKey.String() 541 } 542 return s.Name 543 }