github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/addrs/module_instance.go (about) 1 package addrs 2 3 import ( 4 "bytes" 5 "fmt" 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/hashicorp/terraform/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 for len(remain) > 0 { 86 var next string 87 switch tt := remain[0].(type) { 88 case hcl.TraverseRoot: 89 next = tt.Name 90 case hcl.TraverseAttr: 91 next = tt.Name 92 default: 93 diags = diags.Append(&hcl.Diagnostic{ 94 Severity: hcl.DiagError, 95 Summary: "Invalid address operator", 96 Detail: "Module address prefix must be followed by dot and then a name.", 97 Subject: remain[0].SourceRange().Ptr(), 98 }) 99 break 100 } 101 102 if next != "module" { 103 break 104 } 105 106 kwRange := remain[0].SourceRange() 107 remain = remain[1:] 108 // If we have the prefix "module" then we should be followed by an 109 // module call name, as an attribute, and then optionally an index step 110 // giving the instance key. 111 if len(remain) == 0 { 112 diags = diags.Append(&hcl.Diagnostic{ 113 Severity: hcl.DiagError, 114 Summary: "Invalid address operator", 115 Detail: "Prefix \"module.\" must be followed by a module name.", 116 Subject: &kwRange, 117 }) 118 break 119 } 120 121 var moduleName string 122 switch tt := remain[0].(type) { 123 case hcl.TraverseAttr: 124 moduleName = tt.Name 125 default: 126 diags = diags.Append(&hcl.Diagnostic{ 127 Severity: hcl.DiagError, 128 Summary: "Invalid address operator", 129 Detail: "Prefix \"module.\" must be followed by a module name.", 130 Subject: remain[0].SourceRange().Ptr(), 131 }) 132 break 133 } 134 remain = remain[1:] 135 step := ModuleInstanceStep{ 136 Name: moduleName, 137 } 138 139 if len(remain) > 0 { 140 if idx, ok := remain[0].(hcl.TraverseIndex); ok { 141 remain = remain[1:] 142 143 switch idx.Key.Type() { 144 case cty.String: 145 step.InstanceKey = StringKey(idx.Key.AsString()) 146 case cty.Number: 147 var idxInt int 148 err := gocty.FromCtyValue(idx.Key, &idxInt) 149 if err == nil { 150 step.InstanceKey = IntKey(idxInt) 151 } else { 152 diags = diags.Append(&hcl.Diagnostic{ 153 Severity: hcl.DiagError, 154 Summary: "Invalid address operator", 155 Detail: fmt.Sprintf("Invalid module index: %s.", err), 156 Subject: idx.SourceRange().Ptr(), 157 }) 158 } 159 default: 160 // Should never happen, because no other types are allowed in traversal indices. 161 diags = diags.Append(&hcl.Diagnostic{ 162 Severity: hcl.DiagError, 163 Summary: "Invalid address operator", 164 Detail: "Invalid module key: must be either a string or an integer.", 165 Subject: idx.SourceRange().Ptr(), 166 }) 167 } 168 } 169 } 170 171 mi = append(mi, step) 172 } 173 174 var retRemain hcl.Traversal 175 if len(remain) > 0 { 176 retRemain = make(hcl.Traversal, len(remain)) 177 copy(retRemain, remain) 178 // The first element here might be either a TraverseRoot or a 179 // TraverseAttr, depending on whether we had a module address on the 180 // front. To make life easier for callers, we'll normalize to always 181 // start with a TraverseRoot. 182 if tt, ok := retRemain[0].(hcl.TraverseAttr); ok { 183 retRemain[0] = hcl.TraverseRoot{ 184 Name: tt.Name, 185 SrcRange: tt.SrcRange, 186 } 187 } 188 } 189 190 return mi, retRemain, diags 191 } 192 193 // UnkeyedInstanceShim is a shim method for converting a Module address to the 194 // equivalent ModuleInstance address that assumes that no modules have 195 // keyed instances. 196 // 197 // This is a temporary allowance for the fact that Terraform does not presently 198 // support "count" and "for_each" on modules, and thus graph building code that 199 // derives graph nodes from configuration must just assume unkeyed modules 200 // in order to construct the graph. At a later time when "count" and "for_each" 201 // support is added for modules, all callers of this method will need to be 202 // reworked to allow for keyed module instances. 203 func (m Module) UnkeyedInstanceShim() ModuleInstance { 204 path := make(ModuleInstance, len(m)) 205 for i, name := range m { 206 path[i] = ModuleInstanceStep{Name: name} 207 } 208 return path 209 } 210 211 // ModuleInstanceStep is a single traversal step through the dynamic module 212 // tree. It is used only as part of ModuleInstance. 213 type ModuleInstanceStep struct { 214 Name string 215 InstanceKey InstanceKey 216 } 217 218 // RootModuleInstance is the module instance address representing the root 219 // module, which is also the zero value of ModuleInstance. 220 var RootModuleInstance ModuleInstance 221 222 // IsRoot returns true if the receiver is the address of the root module instance, 223 // or false otherwise. 224 func (m ModuleInstance) IsRoot() bool { 225 return len(m) == 0 226 } 227 228 // Child returns the address of a child module instance of the receiver, 229 // identified by the given name and key. 230 func (m ModuleInstance) Child(name string, key InstanceKey) ModuleInstance { 231 ret := make(ModuleInstance, 0, len(m)+1) 232 ret = append(ret, m...) 233 return append(ret, ModuleInstanceStep{ 234 Name: name, 235 InstanceKey: key, 236 }) 237 } 238 239 // Parent returns the address of the parent module instance of the receiver, or 240 // the receiver itself if there is no parent (if it's the root module address). 241 func (m ModuleInstance) Parent() ModuleInstance { 242 if len(m) == 0 { 243 return m 244 } 245 return m[:len(m)-1] 246 } 247 248 // String returns a string representation of the receiver, in the format used 249 // within e.g. user-provided resource addresses. 250 // 251 // The address of the root module has the empty string as its representation. 252 func (m ModuleInstance) String() string { 253 var buf bytes.Buffer 254 sep := "" 255 for _, step := range m { 256 buf.WriteString(sep) 257 buf.WriteString("module.") 258 buf.WriteString(step.Name) 259 if step.InstanceKey != NoKey { 260 buf.WriteString(step.InstanceKey.String()) 261 } 262 sep = "." 263 } 264 return buf.String() 265 } 266 267 // Equal returns true if the receiver and the given other value 268 // contains the exact same parts. 269 func (m ModuleInstance) Equal(o ModuleInstance) bool { 270 return m.String() == o.String() 271 } 272 273 // Less returns true if the receiver should sort before the given other value 274 // in a sorted list of addresses. 275 func (m ModuleInstance) Less(o ModuleInstance) bool { 276 if len(m) != len(o) { 277 // Shorter path sorts first. 278 return len(m) < len(o) 279 } 280 281 for i := range m { 282 mS, oS := m[i], o[i] 283 switch { 284 case mS.Name != oS.Name: 285 return mS.Name < oS.Name 286 case mS.InstanceKey != oS.InstanceKey: 287 return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey) 288 } 289 } 290 291 return false 292 } 293 294 // Ancestors returns a slice containing the receiver and all of its ancestor 295 // module instances, all the way up to (and including) the root module. 296 // The result is ordered by depth, with the root module always first. 297 // 298 // Since the result always includes the root module, a caller may choose to 299 // ignore it by slicing the result with [1:]. 300 func (m ModuleInstance) Ancestors() []ModuleInstance { 301 ret := make([]ModuleInstance, 0, len(m)+1) 302 for i := 0; i <= len(m); i++ { 303 ret = append(ret, m[:i]) 304 } 305 return ret 306 } 307 308 // IsAncestor returns true if the receiver is an ancestor of the given 309 // other value. 310 func (m ModuleInstance) IsAncestor(o ModuleInstance) bool { 311 // Longer or equal sized paths means the receiver cannot 312 // be an ancestor of the given module insatnce. 313 if len(m) >= len(o) { 314 return false 315 } 316 317 for i, ms := range m { 318 if ms.Name != o[i].Name { 319 return false 320 } 321 if ms.InstanceKey != NoKey && ms.InstanceKey != o[i].InstanceKey { 322 return false 323 } 324 } 325 326 return true 327 } 328 329 // Call returns the module call address that corresponds to the given module 330 // instance, along with the address of the module instance that contains it. 331 // 332 // There is no call for the root module, so this method will panic if called 333 // on the root module address. 334 // 335 // A single module call can produce potentially many module instances, so the 336 // result discards any instance key that might be present on the last step 337 // of the instance. To retain this, use CallInstance instead. 338 // 339 // In practice, this just turns the last element of the receiver into a 340 // ModuleCall and then returns a slice of the receiever that excludes that 341 // last part. This is just a convenience for situations where a call address 342 // is required, such as when dealing with *Reference and Referencable values. 343 func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) { 344 if len(m) == 0 { 345 panic("cannot produce ModuleCall for root module") 346 } 347 348 inst, lastStep := m[:len(m)-1], m[len(m)-1] 349 return inst, ModuleCall{ 350 Name: lastStep.Name, 351 } 352 } 353 354 // CallInstance returns the module call instance address that corresponds to 355 // the given module instance, along with the address of the module instance 356 // that contains it. 357 // 358 // There is no call for the root module, so this method will panic if called 359 // on the root module address. 360 // 361 // In practice, this just turns the last element of the receiver into a 362 // ModuleCallInstance and then returns a slice of the receiever that excludes 363 // that last part. This is just a convenience for situations where a call\ 364 // address is required, such as when dealing with *Reference and Referencable 365 // values. 366 func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) { 367 if len(m) == 0 { 368 panic("cannot produce ModuleCallInstance for root module") 369 } 370 371 inst, lastStep := m[:len(m)-1], m[len(m)-1] 372 return inst, ModuleCallInstance{ 373 Call: ModuleCall{ 374 Name: lastStep.Name, 375 }, 376 Key: lastStep.InstanceKey, 377 } 378 } 379 380 // TargetContains implements Targetable by returning true if the given other 381 // address either matches the receiver, is a sub-module-instance of the 382 // receiver, or is a targetable absolute address within a module that 383 // is contained within the reciever. 384 func (m ModuleInstance) TargetContains(other Targetable) bool { 385 switch to := other.(type) { 386 387 case ModuleInstance: 388 if len(to) < len(m) { 389 // Can't be contained if the path is shorter 390 return false 391 } 392 // Other is contained if its steps match for the length of our own path. 393 for i, ourStep := range m { 394 otherStep := to[i] 395 if ourStep != otherStep { 396 return false 397 } 398 } 399 // If we fall out here then the prefixed matched, so it's contained. 400 return true 401 402 case AbsResource: 403 return m.TargetContains(to.Module) 404 405 case AbsResourceInstance: 406 return m.TargetContains(to.Module) 407 408 default: 409 return false 410 } 411 } 412 413 func (m ModuleInstance) targetableSigil() { 414 // ModuleInstance is targetable 415 }