mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
645 lines
18 KiB
645 lines
18 KiB
// 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)
|
|
}
|
|
}
|