| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package addrs | |
| import ( | |
| "encoding/hex" | |
| "fmt" | |
| "math/rand" | |
| "strings" | |
| "time" | |
| ) | |
| // Resource is an address for a resource block within configuration, which | |
| // contains potentially-multiple resource instances if that configuration | |
| // block uses "count" or "for_each". | |
| type Resource struct { | |
| referenceable | |
| Mode ResourceMode | |
| Type string | |
| Name string | |
| } | |
| func (r Resource) String() string { | |
| switch r.Mode { | |
| case ManagedResourceMode: | |
| return fmt.Sprintf("%s.%s", r.Type, r.Name) | |
| case DataResourceMode: | |
| return fmt.Sprintf("data.%s.%s", r.Type, r.Name) | |
| case EphemeralResourceMode: | |
| return fmt.Sprintf("ephemeral.%s.%s", r.Type, r.Name) | |
| case ListResourceMode: | |
| return fmt.Sprintf("list.%s.%s", r.Type, r.Name) | |
| default: | |
| // Should never happen, but we'll return a string here rather than | |
| // crashing just in case it does. | |
| return fmt.Sprintf("<invalid>.%s.%s", r.Type, r.Name) | |
| } | |
| } | |
| func (r Resource) Equal(o Resource) bool { | |
| return r.Mode == o.Mode && r.Name == o.Name && r.Type == o.Type | |
| } | |
| func (r Resource) Less(o Resource) bool { | |
| switch { | |
| case r.Mode != o.Mode: | |
| return r.Mode == DataResourceMode | |
| case r.Type != o.Type: | |
| return r.Type < o.Type | |
| case r.Name != o.Name: | |
| return r.Name < o.Name | |
| default: | |
| return false | |
| } | |
| } | |
| func (r Resource) UniqueKey() UniqueKey { | |
| return r // A Resource is its own UniqueKey | |
| } | |
| func (r Resource) uniqueKeySigil() {} | |
| // Instance produces the address for a specific instance of the receiver | |
| // that is idenfied by the given key. | |
| func (r Resource) Instance(key InstanceKey) ResourceInstance { | |
| return ResourceInstance{ | |
| Resource: r, | |
| Key: key, | |
| } | |
| } | |
| // Absolute returns an AbsResource from the receiver and the given module | |
| // instance address. | |
| func (r Resource) Absolute(module ModuleInstance) AbsResource { | |
| return AbsResource{ | |
| Module: module, | |
| Resource: r, | |
| } | |
| } | |
| // InModule returns a ConfigResource from the receiver and the given module | |
| // address. | |
| func (r Resource) InModule(module Module) ConfigResource { | |
| return ConfigResource{ | |
| Module: module, | |
| Resource: r, | |
| } | |
| } | |
| // ImpliedProvider returns the implied provider type name, for e.g. the "aws" in | |
| // "aws_instance" | |
| func (r Resource) ImpliedProvider() string { | |
| typeName := r.Type | |
| if under := strings.Index(typeName, "_"); under != -1 { | |
| typeName = typeName[:under] | |
| } | |
| return typeName | |
| } | |
| // ResourceInstance is an address for a specific instance of a resource. | |
| // When a resource is defined in configuration with "count" or "for_each" it | |
| // produces zero or more instances, which can be addressed using this type. | |
| type ResourceInstance struct { | |
| referenceable | |
| Resource Resource | |
| Key InstanceKey | |
| } | |
| func (r ResourceInstance) ContainingResource() Resource { | |
| return r.Resource | |
| } | |
| func (r ResourceInstance) String() string { | |
| if r.Key == NoKey { | |
| return r.Resource.String() | |
| } | |
| return r.Resource.String() + r.Key.String() | |
| } | |
| func (r ResourceInstance) Equal(o ResourceInstance) bool { | |
| return r.Key == o.Key && r.Resource.Equal(o.Resource) | |
| } | |
| func (r ResourceInstance) Less(o ResourceInstance) bool { | |
| if !r.Resource.Equal(o.Resource) { | |
| return r.Resource.Less(o.Resource) | |
| } | |
| if r.Key != o.Key { | |
| return InstanceKeyLess(r.Key, o.Key) | |
| } | |
| return false | |
| } | |
| func (r ResourceInstance) UniqueKey() UniqueKey { | |
| return r // A ResourceInstance is its own UniqueKey | |
| } | |
| func (r ResourceInstance) uniqueKeySigil() {} | |
| // Absolute returns an AbsResourceInstance from the receiver and the given module | |
| // instance address. | |
| func (r ResourceInstance) Absolute(module ModuleInstance) AbsResourceInstance { | |
| return AbsResourceInstance{ | |
| Module: module, | |
| Resource: r, | |
| } | |
| } | |
| // AbsResource is an absolute address for a resource under a given module path. | |
| type AbsResource struct { | |
| targetable | |
| Module ModuleInstance | |
| Resource Resource | |
| } | |
| // Resource returns the address of a particular resource within the receiver. | |
| func (m ModuleInstance) Resource(mode ResourceMode, typeName string, name string) AbsResource { | |
| return AbsResource{ | |
| Module: m, | |
| Resource: Resource{ | |
| Mode: mode, | |
| Type: typeName, | |
| Name: name, | |
| }, | |
| } | |
| } | |
| // Instance produces the address for a specific instance of the receiver | |
| // that is idenfied by the given key. | |
| func (r AbsResource) Instance(key InstanceKey) AbsResourceInstance { | |
| return AbsResourceInstance{ | |
| Module: r.Module, | |
| Resource: r.Resource.Instance(key), | |
| } | |
| } | |
| // Config returns the unexpanded ConfigResource for this AbsResource. | |
| func (r AbsResource) Config() ConfigResource { | |
| return ConfigResource{ | |
| Module: r.Module.Module(), | |
| Resource: r.Resource, | |
| } | |
| } | |
| // TargetContains implements Targetable by returning true if the given other | |
| // address is either equal to the receiver or is an instance of the | |
| // receiver. | |
| func (r AbsResource) TargetContains(other Targetable) bool { | |
| switch to := other.(type) { | |
| case AbsResource: | |
| // We'll use our stringification as a cheat-ish way to test for equality. | |
| return to.String() == r.String() | |
| case ConfigResource: | |
| // if an absolute resource from parsing a target address contains a | |
| // ConfigResource, the string representation will match | |
| return to.String() == r.String() | |
| case AbsResourceInstance: | |
| return r.TargetContains(to.ContainingResource()) | |
| default: | |
| return false | |
| } | |
| } | |
| func (r AbsResource) AddrType() TargetableAddrType { | |
| return AbsResourceAddrType | |
| } | |
| func (r AbsResource) String() string { | |
| if len(r.Module) == 0 { | |
| return r.Resource.String() | |
| } | |
| return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String()) | |
| } | |
| // AffectedAbsResource returns the AbsResource. | |
| func (r AbsResource) AffectedAbsResource() AbsResource { | |
| return r | |
| } | |
| func (r AbsResource) Equal(o AbsResource) bool { | |
| return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource) | |
| } | |
| func (r AbsResource) Less(o AbsResource) bool { | |
| if !r.Module.Equal(o.Module) { | |
| return r.Module.Less(o.Module) | |
| } | |
| if !r.Resource.Equal(o.Resource) { | |
| return r.Resource.Less(o.Resource) | |
| } | |
| return false | |
| } | |
| func (r AbsResource) absMoveableSigil() { | |
| // AbsResource is moveable | |
| } | |
| type absResourceKey string | |
| func (r absResourceKey) uniqueKeySigil() {} | |
| func (r AbsResource) UniqueKey() UniqueKey { | |
| return absResourceKey(r.String()) | |
| } | |
| // AbsResourceInstance is an absolute address for a resource instance under a | |
| // given module path. | |
| type AbsResourceInstance struct { | |
| targetable | |
| Module ModuleInstance | |
| Resource ResourceInstance | |
| } | |
| // ResourceInstance returns the address of a particular resource instance within the receiver. | |
| func (m ModuleInstance) ResourceInstance(mode ResourceMode, typeName string, name string, key InstanceKey) AbsResourceInstance { | |
| return AbsResourceInstance{ | |
| Module: m, | |
| Resource: ResourceInstance{ | |
| Resource: Resource{ | |
| Mode: mode, | |
| Type: typeName, | |
| Name: name, | |
| }, | |
| Key: key, | |
| }, | |
| } | |
| } | |
| // ModuleInstance returns the module instance portion of the address. | |
| func (r AbsResourceInstance) ModuleInstance() ModuleInstance { | |
| return r.Module | |
| } | |
| // ContainingResource returns the address of the resource that contains the | |
| // receving resource instance. In other words, it discards the key portion | |
| // of the address to produce an AbsResource value. | |
| func (r AbsResourceInstance) ContainingResource() AbsResource { | |
| return AbsResource{ | |
| Module: r.Module, | |
| Resource: r.Resource.ContainingResource(), | |
| } | |
| } | |
| // ConfigResource returns the address of the configuration block that declared | |
| // this instance. | |
| func (r AbsResourceInstance) ConfigResource() ConfigResource { | |
| return ConfigResource{ | |
| Module: r.Module.Module(), | |
| Resource: r.Resource.Resource, | |
| } | |
| } | |
| // CurrentObject returns the address of the resource instance's "current" | |
| // object, which is the one used for expression evaluation etc. | |
| func (r AbsResourceInstance) CurrentObject() AbsResourceInstanceObject { | |
| return AbsResourceInstanceObject{ | |
| ResourceInstance: r, | |
| DeposedKey: NotDeposed, | |
| } | |
| } | |
| // DeposedObject returns the address of a "deposed" object for the receiving | |
| // resource instance, which appears only if a create-before-destroy replacement | |
| // succeeds the create step but fails the destroy step, making the original | |
| // object live on as a desposed object. | |
| // | |
| // If the given [DeposedKey] is [NotDeposed] then this is equivalent to | |
| // [AbsResourceInstance.CurrentObject]. | |
| func (r AbsResourceInstance) DeposedObject(key DeposedKey) AbsResourceInstanceObject { | |
| return AbsResourceInstanceObject{ | |
| ResourceInstance: r, | |
| DeposedKey: key, | |
| } | |
| } | |
| // TargetContains implements Targetable by returning true if the given other | |
| // address is equal to the receiver. | |
| func (r AbsResourceInstance) TargetContains(other Targetable) bool { | |
| switch to := other.(type) { | |
| // while we currently don't start with an AbsResourceInstance as a target | |
| // address, check all resource types for consistency. | |
| case AbsResourceInstance: | |
| // We'll use our stringification as a cheat-ish way to test for equality. | |
| return to.String() == r.String() | |
| case ConfigResource: | |
| return to.String() == r.String() | |
| case AbsResource: | |
| return to.String() == r.String() | |
| default: | |
| return false | |
| } | |
| } | |
| func (r AbsResourceInstance) AddrType() TargetableAddrType { | |
| return AbsResourceInstanceAddrType | |
| } | |
| func (r AbsResourceInstance) String() string { | |
| if len(r.Module) == 0 { | |
| return r.Resource.String() | |
| } | |
| return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String()) | |
| } | |
| // AffectedAbsResource returns the AbsResource for the instance. | |
| func (r AbsResourceInstance) AffectedAbsResource() AbsResource { | |
| return AbsResource{ | |
| Module: r.Module, | |
| Resource: r.Resource.Resource, | |
| } | |
| } | |
| func (r AbsResourceInstance) CheckRule(t CheckRuleType, i int) CheckRule { | |
| return CheckRule{ | |
| Container: r, | |
| Type: t, | |
| Index: i, | |
| } | |
| } | |
| func (v AbsResourceInstance) CheckableKind() CheckableKind { | |
| return CheckableResource | |
| } | |
| func (r AbsResourceInstance) Equal(o AbsResourceInstance) bool { | |
| return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource) | |
| } | |
| // Less returns true if the receiver should sort before the given other value | |
| // in a sorted list of addresses. | |
| func (r AbsResourceInstance) Less(o AbsResourceInstance) bool { | |
| if !r.Module.Equal(o.Module) { | |
| return r.Module.Less(o.Module) | |
| } | |
| if !r.Resource.Equal(o.Resource) { | |
| return r.Resource.Less(o.Resource) | |
| } | |
| return false | |
| } | |
| // AbsResourceInstance is a Checkable | |
| func (r AbsResourceInstance) checkableSigil() {} | |
| func (r AbsResourceInstance) ConfigCheckable() ConfigCheckable { | |
| // The ConfigCheckable for an AbsResourceInstance is its ConfigResource. | |
| return r.ConfigResource() | |
| } | |
| type absResourceInstanceKey string | |
| func (r AbsResourceInstance) UniqueKey() UniqueKey { | |
| return absResourceInstanceKey(r.String()) | |
| } | |
| func (r absResourceInstanceKey) uniqueKeySigil() {} | |
| func (r AbsResourceInstance) absMoveableSigil() { | |
| // AbsResourceInstance is moveable | |
| } | |
| // ConfigResource is an address for a resource within a configuration. | |
| type ConfigResource struct { | |
| targetable | |
| Module Module | |
| Resource Resource | |
| } | |
| // Resource returns the address of a particular resource within the module. | |
| func (m Module) Resource(mode ResourceMode, typeName string, name string) ConfigResource { | |
| return ConfigResource{ | |
| Module: m, | |
| Resource: Resource{ | |
| Mode: mode, | |
| Type: typeName, | |
| Name: name, | |
| }, | |
| } | |
| } | |
| // Absolute produces the address for the receiver within a specific module instance. | |
| func (r ConfigResource) Absolute(module ModuleInstance) AbsResource { | |
| return AbsResource{ | |
| Module: module, | |
| Resource: r.Resource, | |
| } | |
| } | |
| // TargetContains implements Targetable by returning true if the given other | |
| // address is either equal to the receiver or is an instance of the | |
| // receiver. | |
| func (r ConfigResource) TargetContains(other Targetable) bool { | |
| switch to := other.(type) { | |
| case ConfigResource: | |
| // We'll use our stringification as a cheat-ish way to test for equality. | |
| return to.String() == r.String() | |
| case AbsResource: | |
| return r.TargetContains(to.Config()) | |
| case AbsResourceInstance: | |
| return r.TargetContains(to.ContainingResource()) | |
| default: | |
| return false | |
| } | |
| } | |
| func (r ConfigResource) AddrType() TargetableAddrType { | |
| return ConfigResourceAddrType | |
| } | |
| func (r ConfigResource) String() string { | |
| if len(r.Module) == 0 { | |
| return r.Resource.String() | |
| } | |
| return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String()) | |
| } | |
| func (r ConfigResource) Equal(o ConfigResource) bool { | |
| return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource) | |
| } | |
| func (r ConfigResource) UniqueKey() UniqueKey { | |
| return configResourceKey(r.String()) | |
| } | |
| func (r ConfigResource) configMoveableSigil() { | |
| // ConfigResource is moveable | |
| } | |
| func (r ConfigResource) configCheckableSigil() { | |
| // ConfigResource represents a configuration object that declares checkable objects | |
| } | |
| func (v ConfigResource) CheckableKind() CheckableKind { | |
| return CheckableResource | |
| } | |
| type configResourceKey string | |
| func (k configResourceKey) uniqueKeySigil() {} | |
| // ResourceMode defines which lifecycle applies to a given resource. Each | |
| // resource lifecycle has a slightly different address format. | |
| type ResourceMode rune | |
| //go:generate go tool golang.org/x/tools/cmd/stringer -type ResourceMode | |
| const ( | |
| // InvalidResourceMode is the zero value of ResourceMode and is not | |
| // a valid resource mode. | |
| InvalidResourceMode ResourceMode = 0 | |
| // ManagedResourceMode indicates a managed resource, as defined by | |
| // "resource" blocks in configuration. | |
| ManagedResourceMode ResourceMode = 'M' | |
| // DataResourceMode indicates a data resource, as defined by | |
| // "data" blocks in configuration. | |
| DataResourceMode ResourceMode = 'D' | |
| // EphemeralResourceMode indicates an ephemeral resource, as defined by | |
| // "ephemeral" blocks in configuration. | |
| EphemeralResourceMode ResourceMode = 'E' | |
| // ListResourceMode indicates a list resource, as defined by | |
| // "list" blocks in tfquery configuration. | |
| ListResourceMode ResourceMode = 'L' | |
| ) | |
| // AbsResourceInstanceObject represents one of the specific remote objects | |
| // associated with a resource instance. | |
| // | |
| // When DeposedKey is [NotDeposed], this represents the "current" object. | |
| // Otherwise, this represents a deposed object with the given key. | |
| // | |
| // The distinction between "current" and "deposed" objects is a planning and | |
| // state concern that isn't reflected directly in configuration, so there | |
| // are no "ConfigResourceInstanceObject" or "ResourceInstanceObject" address | |
| // types. | |
| type AbsResourceInstanceObject struct { | |
| ResourceInstance AbsResourceInstance | |
| DeposedKey DeposedKey | |
| } | |
| // String returns a string that could be used to refer to this object | |
| // in the UI, but is not necessarily suitable for use as a unique key. | |
| func (o AbsResourceInstanceObject) String() string { | |
| if o.DeposedKey != NotDeposed { | |
| return fmt.Sprintf("%s deposed object %s", o.ResourceInstance, o.DeposedKey) | |
| } | |
| return o.ResourceInstance.String() | |
| } | |
| // IsCurrent returns true only if this address is for a "current" object. | |
| func (o AbsResourceInstanceObject) IsCurrent() bool { | |
| return o.DeposedKey == NotDeposed | |
| } | |
| // IsCurrent returns true only if this address is for a "deposed" object. | |
| func (o AbsResourceInstanceObject) IsDeposed() bool { | |
| return o.DeposedKey != NotDeposed | |
| } | |
| // UniqueKey implements [UniqueKeyer] | |
| func (o AbsResourceInstanceObject) UniqueKey() UniqueKey { | |
| return absResourceInstanceObjectKey{ | |
| resourceInstanceKey: o.ResourceInstance.UniqueKey(), | |
| deposedKey: o.DeposedKey, | |
| } | |
| } | |
| // Less describes the "natural order" of resource instance object addresses. | |
| // | |
| // Objects that differ in the resource instance address sort in the natural | |
| // order of AbsResourceInstance. Objects belonging to the same resource | |
| // instance sort by deposed key, with non-deposed ("current") objects sorting | |
| // first. | |
| func (o AbsResourceInstanceObject) Less(other AbsResourceInstanceObject) bool { | |
| switch { | |
| case !o.ResourceInstance.Equal(other.ResourceInstance): | |
| return o.ResourceInstance.Less(other.ResourceInstance) | |
| default: | |
| return o.DeposedKey < other.DeposedKey | |
| } | |
| } | |
| type absResourceInstanceObjectKey struct { | |
| resourceInstanceKey UniqueKey | |
| deposedKey DeposedKey | |
| } | |
| func (absResourceInstanceObjectKey) uniqueKeySigil() {} | |
| // DeposedKey is a 8-character hex string used to uniquely identify deposed | |
| // instance objects in the state. | |
| // | |
| // The zero value of this type is [NotDeposed] and represents a "current" | |
| // object, not deposed at all. All other valid values of this type are strings | |
| // containing exactly eight lowercase hex characters. | |
| type DeposedKey string | |
| // NotDeposed is a special invalid value of DeposedKey that is used to represent | |
| // the absense of a deposed key, typically when referring to the "current" object | |
| // for a particular resource instance. It must not be used as an actual deposed | |
| // key. | |
| const NotDeposed = DeposedKey("") | |
| var deposedKeyRand = rand.New(rand.NewSource(time.Now().UnixNano())) | |
| // NewDeposedKey generates a pseudo-random deposed key. Because of the short | |
| // length of these keys, uniqueness is not a natural consequence and so the | |
| // caller should test to see if the generated key is already in use and generate | |
| // another if so, until a unique key is found. | |
| func NewDeposedKey() DeposedKey { | |
| v := deposedKeyRand.Uint32() | |
| return DeposedKey(fmt.Sprintf("%08x", v)) | |
| } | |
| // ParseDeposedKey parses a string that is expected to be a deposed key, | |
| // returning an error if it doesn't conform to the expected syntax. | |
| func ParseDeposedKey(raw string) (DeposedKey, error) { | |
| if len(raw) != 8 { | |
| return "00000000", fmt.Errorf("must be eight hexadecimal digits") | |
| } | |
| if raw != strings.ToLower(raw) { | |
| return "00000000", fmt.Errorf("must use lowercase hex digits") | |
| } | |
| _, err := hex.DecodeString(raw) | |
| if err != nil { | |
| return "00000000", fmt.Errorf("must be eight hexadecimal digits") | |
| } | |
| return DeposedKey(raw), nil | |
| } | |
| func (k DeposedKey) String() string { | |
| return string(k) | |
| } | |
| func (k DeposedKey) GoString() string { | |
| ks := string(k) | |
| switch { | |
| case ks == "": | |
| return "states.NotDeposed" | |
| default: | |
| return fmt.Sprintf("states.DeposedKey(%q)", ks) | |
| } | |
| } |