addrs: UniqueKey and UniqueKeyer
Many times now we've seen situations where we need to use addresses as map keys, but not all of our address types are comparable and thus we tend to end up using string representations as keys instead. That's problematic because conversion to string uses type information and some of the address types have string representations that are ambiguous with one another. UniqueKey therefore represents an opaque key that is unique for each functionally-distinct address across all types that implement UniqueKeyer. For this initial commit I've implemented UniqueKeyer only for the Referenceable family of types. These are an easy case because they were all already comparable (intentionally) anyway. Later commits can implement UniqueKeyer for other types that are not naturally comparable, such as any which include a ModuleInstance. This also includes a new type addrs.Set which wraps a map as a set of addresses, using the unique keys to ensure that there can be only one element for each distinct address.
This commit is contained in:
parent
7cd333dea1
commit
f3a57db293
|
@ -10,3 +10,9 @@ type CountAttr struct {
|
|||
func (ca CountAttr) String() string {
|
||||
return "count." + ca.Name
|
||||
}
|
||||
|
||||
func (ca CountAttr) UniqueKey() UniqueKey {
|
||||
return ca // A CountAttr is its own UniqueKey
|
||||
}
|
||||
|
||||
func (ca CountAttr) uniqueKeySigil() {}
|
||||
|
|
|
@ -10,3 +10,9 @@ type ForEachAttr struct {
|
|||
func (f ForEachAttr) String() string {
|
||||
return "each." + f.Name
|
||||
}
|
||||
|
||||
func (f ForEachAttr) UniqueKey() UniqueKey {
|
||||
return f // A ForEachAttr is its own UniqueKey
|
||||
}
|
||||
|
||||
func (f ForEachAttr) uniqueKeySigil() {}
|
||||
|
|
|
@ -14,6 +14,12 @@ func (v InputVariable) String() string {
|
|||
return "var." + v.Name
|
||||
}
|
||||
|
||||
func (v InputVariable) UniqueKey() UniqueKey {
|
||||
return v // A InputVariable is its own UniqueKey
|
||||
}
|
||||
|
||||
func (v InputVariable) uniqueKeySigil() {}
|
||||
|
||||
// Absolute converts the receiver into an absolute address within the given
|
||||
// module instance.
|
||||
func (v InputVariable) Absolute(m ModuleInstance) AbsInputVariableInstance {
|
||||
|
|
|
@ -14,6 +14,12 @@ func (v LocalValue) String() string {
|
|||
return "local." + v.Name
|
||||
}
|
||||
|
||||
func (v LocalValue) UniqueKey() UniqueKey {
|
||||
return v // A LocalValue is its own UniqueKey
|
||||
}
|
||||
|
||||
func (v LocalValue) uniqueKeySigil() {}
|
||||
|
||||
// Absolute converts the receiver into an absolute address within the given
|
||||
// module instance.
|
||||
func (v LocalValue) Absolute(m ModuleInstance) AbsLocalValue {
|
||||
|
|
|
@ -15,6 +15,12 @@ func (c ModuleCall) String() string {
|
|||
return "module." + c.Name
|
||||
}
|
||||
|
||||
func (c ModuleCall) UniqueKey() UniqueKey {
|
||||
return c // A ModuleCall is its own UniqueKey
|
||||
}
|
||||
|
||||
func (c ModuleCall) uniqueKeySigil() {}
|
||||
|
||||
// Instance returns the address of an instance of the receiver identified by
|
||||
// the given key.
|
||||
func (c ModuleCall) Instance(key InstanceKey) ModuleCallInstance {
|
||||
|
@ -79,6 +85,12 @@ func (c ModuleCallInstance) String() string {
|
|||
return fmt.Sprintf("module.%s%s", c.Call.Name, c.Key)
|
||||
}
|
||||
|
||||
func (c ModuleCallInstance) UniqueKey() UniqueKey {
|
||||
return c // A ModuleCallInstance is its own UniqueKey
|
||||
}
|
||||
|
||||
func (c ModuleCallInstance) uniqueKeySigil() {}
|
||||
|
||||
func (c ModuleCallInstance) Absolute(moduleAddr ModuleInstance) ModuleInstance {
|
||||
ret := make(ModuleInstance, len(moduleAddr), len(moduleAddr)+1)
|
||||
copy(ret, moduleAddr)
|
||||
|
@ -118,6 +130,12 @@ func (m ModuleCallOutput) String() string {
|
|||
return fmt.Sprintf("%s.%s", m.Call.String(), m.Name)
|
||||
}
|
||||
|
||||
func (m ModuleCallOutput) UniqueKey() UniqueKey {
|
||||
return m // A ModuleCallOutput is its own UniqueKey
|
||||
}
|
||||
|
||||
func (m ModuleCallOutput) uniqueKeySigil() {}
|
||||
|
||||
// ModuleCallInstanceOutput is the address of a particular named output produced by
|
||||
// an instance of a module call.
|
||||
type ModuleCallInstanceOutput struct {
|
||||
|
@ -139,6 +157,12 @@ func (co ModuleCallInstanceOutput) String() string {
|
|||
return fmt.Sprintf("%s.%s", co.Call.String(), co.Name)
|
||||
}
|
||||
|
||||
func (co ModuleCallInstanceOutput) UniqueKey() UniqueKey {
|
||||
return co // A ModuleCallInstanceOutput is its own UniqueKey
|
||||
}
|
||||
|
||||
func (co ModuleCallInstanceOutput) uniqueKeySigil() {}
|
||||
|
||||
// AbsOutputValue returns the absolute output value address that corresponds
|
||||
// to the receving module call output address, once resolved in the given
|
||||
// calling module.
|
||||
|
|
|
@ -10,3 +10,9 @@ type PathAttr struct {
|
|||
func (pa PathAttr) String() string {
|
||||
return "path." + pa.Name
|
||||
}
|
||||
|
||||
func (pa PathAttr) UniqueKey() UniqueKey {
|
||||
return pa // A PathAttr is its own UniqueKey
|
||||
}
|
||||
|
||||
func (pa PathAttr) uniqueKeySigil() {}
|
||||
|
|
|
@ -7,6 +7,9 @@ type Referenceable interface {
|
|||
// in lang.Scope.buildEvalContext.
|
||||
referenceableSigil()
|
||||
|
||||
// All Referenceable address types must have unique keys.
|
||||
UniqueKeyer
|
||||
|
||||
// String produces a string representation of the address that could be
|
||||
// parsed as a HCL traversal and passed to ParseRef to produce an identical
|
||||
// result.
|
||||
|
|
|
@ -32,6 +32,12 @@ func (r Resource) Equal(o Resource) bool {
|
|||
return r.Mode == o.Mode && r.Name == o.Name && r.Type == o.Type
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -94,6 +100,12 @@ func (r ResourceInstance) Equal(o ResourceInstance) bool {
|
|||
return r.Key == o.Key && r.Resource.Equal(o.Resource)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -44,6 +44,12 @@ func (rp ResourceInstancePhase) String() string {
|
|||
return fmt.Sprintf("%s#%s", rp.ResourceInstance, rp.Phase)
|
||||
}
|
||||
|
||||
func (rp ResourceInstancePhase) UniqueKey() UniqueKey {
|
||||
return rp // A ResourceInstancePhase is its own UniqueKey
|
||||
}
|
||||
|
||||
func (rp ResourceInstancePhase) uniqueKeySigil() {}
|
||||
|
||||
// ResourceInstancePhaseType is an enumeration used with ResourceInstancePhase.
|
||||
type ResourceInstancePhaseType string
|
||||
|
||||
|
@ -103,3 +109,9 @@ func (rp ResourcePhase) String() string {
|
|||
// because this special address type should never be exposed in the UI.
|
||||
return fmt.Sprintf("%s#%s", rp.Resource, rp.Phase)
|
||||
}
|
||||
|
||||
func (rp ResourcePhase) UniqueKey() UniqueKey {
|
||||
return rp // A ResourcePhase is its own UniqueKey
|
||||
}
|
||||
|
||||
func (rp ResourcePhase) uniqueKeySigil() {}
|
||||
|
|
|
@ -12,3 +12,9 @@ func (s selfT) referenceableSigil() {
|
|||
func (s selfT) String() string {
|
||||
return "self"
|
||||
}
|
||||
|
||||
func (s selfT) UniqueKey() UniqueKey {
|
||||
return Self // Self is its own UniqueKey
|
||||
}
|
||||
|
||||
func (s selfT) uniqueKeySigil() {}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package addrs
|
||||
|
||||
// Set represents a set of addresses of types that implement UniqueKeyer.
|
||||
type Set map[UniqueKey]UniqueKeyer
|
||||
|
||||
func (s Set) Has(addr UniqueKeyer) bool {
|
||||
_, exists := s[addr.UniqueKey()]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s Set) Add(addr UniqueKeyer) {
|
||||
s[addr.UniqueKey()] = addr
|
||||
}
|
||||
|
||||
func (s Set) Remove(addr UniqueKeyer) {
|
||||
delete(s, addr.UniqueKey())
|
||||
}
|
||||
|
||||
func (s Set) Union(other Set) Set {
|
||||
ret := make(Set)
|
||||
for k, addr := range s {
|
||||
ret[k] = addr
|
||||
}
|
||||
for k, addr := range other {
|
||||
ret[k] = addr
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s Set) Intersection(other Set) Set {
|
||||
ret := make(Set)
|
||||
for k, addr := range s {
|
||||
if _, exists := other[k]; exists {
|
||||
ret[k] = addr
|
||||
}
|
||||
}
|
||||
for k, addr := range other {
|
||||
if _, exists := s[k]; exists {
|
||||
ret[k] = addr
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -10,3 +10,9 @@ type TerraformAttr struct {
|
|||
func (ta TerraformAttr) String() string {
|
||||
return "terraform." + ta.Name
|
||||
}
|
||||
|
||||
func (ta TerraformAttr) UniqueKey() UniqueKey {
|
||||
return ta // A TerraformAttr is its own UniqueKey
|
||||
}
|
||||
|
||||
func (ta TerraformAttr) uniqueKeySigil() {}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package addrs
|
||||
|
||||
// UniqueKey is an interface implemented by values that serve as unique map
|
||||
// keys for particular addresses.
|
||||
//
|
||||
// All implementations of UniqueKey are comparable and can thus be used as
|
||||
// map keys. Unique keys generated from different address types are always
|
||||
// distinct. All functionally-equivalent keys for the same address type
|
||||
// always compare equal, and likewise functionally-different values do not.
|
||||
type UniqueKey interface {
|
||||
uniqueKeySigil()
|
||||
}
|
||||
|
||||
// UniqueKeyer is an interface implemented by types that can be represented
|
||||
// by a unique key.
|
||||
//
|
||||
// Some address types naturally comply with the expectations of a UniqueKey
|
||||
// and may thus be their own unique key type. However, address types that
|
||||
// are not naturally comparable can implement this interface by returning
|
||||
// proxy values.
|
||||
type UniqueKeyer interface {
|
||||
UniqueKey() UniqueKey
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestUniqueKeyer aims to ensure that all of the types that have unique keys
|
||||
// will continue to meet the UniqueKeyer contract under future changes.
|
||||
//
|
||||
// If you add a new implementation of UniqueKey, consider adding a test case
|
||||
// for it here.
|
||||
func TestUniqueKeyer(t *testing.T) {
|
||||
tests := []UniqueKeyer{
|
||||
CountAttr{Name: "index"},
|
||||
ForEachAttr{Name: "key"},
|
||||
TerraformAttr{Name: "workspace"},
|
||||
PathAttr{Name: "module"},
|
||||
InputVariable{Name: "foo"},
|
||||
ModuleCall{Name: "foo"},
|
||||
ModuleCallInstance{
|
||||
Call: ModuleCall{Name: "foo"},
|
||||
Key: StringKey("a"),
|
||||
},
|
||||
ModuleCallOutput{
|
||||
Call: ModuleCall{Name: "foo"},
|
||||
Name: "bar",
|
||||
},
|
||||
ModuleCallInstanceOutput{
|
||||
Call: ModuleCallInstance{
|
||||
Call: ModuleCall{Name: "foo"},
|
||||
Key: StringKey("a"),
|
||||
},
|
||||
Name: "bar",
|
||||
},
|
||||
Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
ResourceInstance{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
Key: IntKey(1),
|
||||
},
|
||||
Self,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s", test), func(t *testing.T) {
|
||||
a := test.UniqueKey()
|
||||
b := test.UniqueKey()
|
||||
|
||||
// The following comparison will panic if the unique key is not
|
||||
// of a comparable type.
|
||||
if a != b {
|
||||
t.Fatalf("the two unique keys are not equal\na: %#v\b: %#v", a, b)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue