terraform: State.Equal
This commit is contained in:
parent
85e2bef179
commit
b041f48e56
|
@ -134,6 +134,30 @@ func (s *State) RootModule() *ModuleState {
|
|||
return root
|
||||
}
|
||||
|
||||
// Equal tests if one state is equal to another.
|
||||
func (s *State) Equal(other *State) bool {
|
||||
// If the versions are different, they're certainly not equal
|
||||
if s.Version != other.Version {
|
||||
return false
|
||||
}
|
||||
|
||||
// If any of the modules are not equal, then this state isn't equal
|
||||
for _, m := range s.Modules {
|
||||
// This isn't very optimal currently but works.
|
||||
otherM := other.ModuleByPath(m.Path)
|
||||
if otherM == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If they're not equal, then we're not equal!
|
||||
if !m.Equal(otherM) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *State) init() {
|
||||
if s.Version == 0 {
|
||||
s.Version = StateVersion
|
||||
|
@ -289,6 +313,54 @@ type ModuleState struct {
|
|||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
}
|
||||
|
||||
// Equal tests whether one module state is equal to another.
|
||||
func (m *ModuleState) Equal(other *ModuleState) bool {
|
||||
// Paths must be equal
|
||||
if !reflect.DeepEqual(m.Path, other.Path) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Outputs must be equal
|
||||
if len(m.Outputs) != len(other.Outputs) {
|
||||
return false
|
||||
}
|
||||
for k, v := range m.Outputs {
|
||||
if other.Outputs[k] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies must be equal. This sorts these in place but
|
||||
// this shouldn't cause any problems.
|
||||
sort.Strings(m.Dependencies)
|
||||
sort.Strings(other.Dependencies)
|
||||
if len(m.Dependencies) != len(other.Dependencies) {
|
||||
return false
|
||||
}
|
||||
for i, d := range m.Dependencies {
|
||||
if other.Dependencies[i] != d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Resources must be equal
|
||||
if len(m.Resources) != len(other.Resources) {
|
||||
return false
|
||||
}
|
||||
for k, r := range m.Resources {
|
||||
otherR, ok := other.Resources[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !r.Equal(otherR) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsRoot says whether or not this module diff is for the root module.
|
||||
func (m *ModuleState) IsRoot() bool {
|
||||
return reflect.DeepEqual(m.Path, rootModulePath)
|
||||
|
@ -522,6 +594,63 @@ type ResourceState struct {
|
|||
Tainted []*InstanceState `json:"tainted,omitempty"`
|
||||
}
|
||||
|
||||
// Equal tests whether two ResourceStates are equal.
|
||||
func (s *ResourceState) Equal(other *ResourceState) bool {
|
||||
if s.Type != other.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
// Dependencies must be equal
|
||||
sort.Strings(s.Dependencies)
|
||||
sort.Strings(other.Dependencies)
|
||||
if len(s.Dependencies) != len(other.Dependencies) {
|
||||
return false
|
||||
}
|
||||
for i, d := range s.Dependencies {
|
||||
if other.Dependencies[i] != d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// States must be equal
|
||||
if !s.Primary.Equal(other.Primary) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Tainted
|
||||
taints := make(map[string]*InstanceState)
|
||||
for _, t := range other.Tainted {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
taints[t.ID] = t
|
||||
}
|
||||
for _, t := range s.Tainted {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
otherT, ok := taints[t.ID]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(taints, t.ID)
|
||||
|
||||
if !t.Equal(otherT) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// This means that we have stuff in other tainted that we don't
|
||||
// have, so it is not equal.
|
||||
if len(taints) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *ResourceState) init() {
|
||||
if r.Primary == nil {
|
||||
r.Primary = &InstanceState{}
|
||||
|
@ -618,6 +747,35 @@ func (i *InstanceState) deepcopy() *InstanceState {
|
|||
return n
|
||||
}
|
||||
|
||||
func (s *InstanceState) Equal(other *InstanceState) bool {
|
||||
// Short circuit some nil checks
|
||||
if s == nil || other == nil {
|
||||
return s == other
|
||||
}
|
||||
|
||||
// IDs must be equal
|
||||
if s.ID != other.ID {
|
||||
return false
|
||||
}
|
||||
|
||||
// Attributes must be equal
|
||||
if len(s.Attributes) != len(other.Attributes) {
|
||||
return false
|
||||
}
|
||||
for k, v := range s.Attributes {
|
||||
otherV, ok := other.Attributes[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if v != otherV {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MergeDiff takes a ResourceDiff and merges the attributes into
|
||||
// this resource state in order to generate a new state. This new
|
||||
// state can be used to provide updated attribute lookups for
|
||||
|
|
|
@ -111,6 +111,207 @@ func TestStateModuleOrphans_nilConfig(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStateEqual(t *testing.T) {
|
||||
cases := []struct {
|
||||
Result bool
|
||||
One, Two *State
|
||||
}{
|
||||
// Different versions
|
||||
{
|
||||
false,
|
||||
&State{Version: 5},
|
||||
&State{Version: 2},
|
||||
},
|
||||
|
||||
// Different modules
|
||||
{
|
||||
false,
|
||||
&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
},
|
||||
},
|
||||
},
|
||||
&State{},
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
},
|
||||
},
|
||||
},
|
||||
&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
if tc.One.Equal(tc.Two) != tc.Result {
|
||||
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceStateEqual(t *testing.T) {
|
||||
cases := []struct {
|
||||
Result bool
|
||||
One, Two *ResourceState
|
||||
}{
|
||||
// Different types
|
||||
{
|
||||
false,
|
||||
&ResourceState{Type: "foo"},
|
||||
&ResourceState{Type: "bar"},
|
||||
},
|
||||
|
||||
// Different dependencies
|
||||
{
|
||||
false,
|
||||
&ResourceState{Dependencies: []string{"foo"}},
|
||||
&ResourceState{Dependencies: []string{"bar"}},
|
||||
},
|
||||
|
||||
{
|
||||
false,
|
||||
&ResourceState{Dependencies: []string{"foo", "bar"}},
|
||||
&ResourceState{Dependencies: []string{"foo"}},
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
&ResourceState{Dependencies: []string{"bar", "foo"}},
|
||||
&ResourceState{Dependencies: []string{"foo", "bar"}},
|
||||
},
|
||||
|
||||
// Different primaries
|
||||
{
|
||||
false,
|
||||
&ResourceState{Primary: nil},
|
||||
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
||||
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
||||
},
|
||||
|
||||
// Different tainted
|
||||
{
|
||||
false,
|
||||
&ResourceState{
|
||||
Tainted: nil,
|
||||
},
|
||||
&ResourceState{
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{ID: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
&ResourceState{
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{ID: "foo"},
|
||||
},
|
||||
},
|
||||
&ResourceState{
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{ID: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
true,
|
||||
&ResourceState{
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{ID: "foo"},
|
||||
nil,
|
||||
},
|
||||
},
|
||||
&ResourceState{
|
||||
Tainted: []*InstanceState{
|
||||
&InstanceState{ID: "foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
if tc.One.Equal(tc.Two) != tc.Result {
|
||||
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||
}
|
||||
if tc.Two.Equal(tc.One) != tc.Result {
|
||||
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceStateEqual(t *testing.T) {
|
||||
cases := []struct {
|
||||
Result bool
|
||||
One, Two *InstanceState
|
||||
}{
|
||||
// Nils
|
||||
{
|
||||
false,
|
||||
nil,
|
||||
&InstanceState{},
|
||||
},
|
||||
|
||||
{
|
||||
false,
|
||||
&InstanceState{},
|
||||
nil,
|
||||
},
|
||||
|
||||
// Different IDs
|
||||
{
|
||||
false,
|
||||
&InstanceState{ID: "foo"},
|
||||
&InstanceState{ID: "bar"},
|
||||
},
|
||||
|
||||
// Different Attributes
|
||||
{
|
||||
false,
|
||||
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
||||
&InstanceState{Attributes: map[string]string{"foo": "baz"}},
|
||||
},
|
||||
|
||||
// Different Attribute keys
|
||||
{
|
||||
false,
|
||||
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
||||
&InstanceState{Attributes: map[string]string{"bar": "baz"}},
|
||||
},
|
||||
|
||||
{
|
||||
false,
|
||||
&InstanceState{Attributes: map[string]string{"bar": "baz"}},
|
||||
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
if tc.One.Equal(tc.Two) != tc.Result {
|
||||
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceState_MergeDiff(t *testing.T) {
|
||||
is := InstanceState{
|
||||
ID: "foo",
|
||||
|
|
Loading…
Reference in New Issue