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