github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/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-plugin-sdk/internal/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 // ParseProviderConfigCompact. 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 // Less returns true if the receiver should sort before the given other value 268 // in a sorted list of addresses. 269 func (m ModuleInstance) Less(o ModuleInstance) bool { 270 if len(m) != len(o) { 271 // Shorter path sorts first. 272 return len(m) < len(o) 273 } 274 275 for i := range m { 276 mS, oS := m[i], o[i] 277 switch { 278 case mS.Name != oS.Name: 279 return mS.Name < oS.Name 280 case mS.InstanceKey != oS.InstanceKey: 281 return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey) 282 } 283 } 284 285 return false 286 } 287 288 // Ancestors returns a slice containing the receiver and all of its ancestor 289 // module instances, all the way up to (and including) the root module. 290 // The result is ordered by depth, with the root module always first. 291 // 292 // Since the result always includes the root module, a caller may choose to 293 // ignore it by slicing the result with [1:]. 294 func (m ModuleInstance) Ancestors() []ModuleInstance { 295 ret := make([]ModuleInstance, 0, len(m)+1) 296 for i := 0; i <= len(m); i++ { 297 ret = append(ret, m[:i]) 298 } 299 return ret 300 } 301 302 // Call returns the module call address that corresponds to the given module 303 // instance, along with the address of the module instance that contains it. 304 // 305 // There is no call for the root module, so this method will panic if called 306 // on the root module address. 307 // 308 // A single module call can produce potentially many module instances, so the 309 // result discards any instance key that might be present on the last step 310 // of the instance. To retain this, use CallInstance instead. 311 // 312 // In practice, this just turns the last element of the receiver into a 313 // ModuleCall and then returns a slice of the receiever that excludes that 314 // last part. This is just a convenience for situations where a call address 315 // is required, such as when dealing with *Reference and Referencable values. 316 func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) { 317 if len(m) == 0 { 318 panic("cannot produce ModuleCall for root module") 319 } 320 321 inst, lastStep := m[:len(m)-1], m[len(m)-1] 322 return inst, ModuleCall{ 323 Name: lastStep.Name, 324 } 325 } 326 327 // CallInstance returns the module call instance address that corresponds to 328 // the given module instance, along with the address of the module instance 329 // that contains it. 330 // 331 // There is no call for the root module, so this method will panic if called 332 // on the root module address. 333 // 334 // In practice, this just turns the last element of the receiver into a 335 // ModuleCallInstance and then returns a slice of the receiever that excludes 336 // that last part. This is just a convenience for situations where a call\ 337 // address is required, such as when dealing with *Reference and Referencable 338 // values. 339 func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) { 340 if len(m) == 0 { 341 panic("cannot produce ModuleCallInstance for root module") 342 } 343 344 inst, lastStep := m[:len(m)-1], m[len(m)-1] 345 return inst, ModuleCallInstance{ 346 Call: ModuleCall{ 347 Name: lastStep.Name, 348 }, 349 Key: lastStep.InstanceKey, 350 } 351 } 352 353 // TargetContains implements Targetable by returning true if the given other 354 // address either matches the receiver, is a sub-module-instance of the 355 // receiver, or is a targetable absolute address within a module that 356 // is contained within the reciever. 357 func (m ModuleInstance) TargetContains(other Targetable) bool { 358 switch to := other.(type) { 359 360 case ModuleInstance: 361 if len(to) < len(m) { 362 // Can't be contained if the path is shorter 363 return false 364 } 365 // Other is contained if its steps match for the length of our own path. 366 for i, ourStep := range m { 367 otherStep := to[i] 368 if ourStep != otherStep { 369 return false 370 } 371 } 372 // If we fall out here then the prefixed matched, so it's contained. 373 return true 374 375 case AbsResource: 376 return m.TargetContains(to.Module) 377 378 case AbsResourceInstance: 379 return m.TargetContains(to.Module) 380 381 default: 382 return false 383 } 384 } 385 386 func (m ModuleInstance) targetableSigil() { 387 // ModuleInstance is targetable 388 }