terraform: State.Equal
This commit is contained in:
parent
85e2bef179
commit
b041f48e56
|
@ -134,6 +134,30 @@ func (s *State) RootModule() *ModuleState {
|
||||||
return root
|
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() {
|
func (s *State) init() {
|
||||||
if s.Version == 0 {
|
if s.Version == 0 {
|
||||||
s.Version = StateVersion
|
s.Version = StateVersion
|
||||||
|
@ -289,6 +313,54 @@ type ModuleState struct {
|
||||||
Dependencies []string `json:"depends_on,omitempty"`
|
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.
|
// IsRoot says whether or not this module diff is for the root module.
|
||||||
func (m *ModuleState) IsRoot() bool {
|
func (m *ModuleState) IsRoot() bool {
|
||||||
return reflect.DeepEqual(m.Path, rootModulePath)
|
return reflect.DeepEqual(m.Path, rootModulePath)
|
||||||
|
@ -522,6 +594,63 @@ type ResourceState struct {
|
||||||
Tainted []*InstanceState `json:"tainted,omitempty"`
|
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() {
|
func (r *ResourceState) init() {
|
||||||
if r.Primary == nil {
|
if r.Primary == nil {
|
||||||
r.Primary = &InstanceState{}
|
r.Primary = &InstanceState{}
|
||||||
|
@ -618,6 +747,35 @@ func (i *InstanceState) deepcopy() *InstanceState {
|
||||||
return n
|
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
|
// MergeDiff takes a ResourceDiff and merges the attributes into
|
||||||
// this resource state in order to generate a new state. This new
|
// this resource state in order to generate a new state. This new
|
||||||
// state can be used to provide updated attribute lookups for
|
// 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) {
|
func TestInstanceState_MergeDiff(t *testing.T) {
|
||||||
is := InstanceState{
|
is := InstanceState{
|
||||||
ID: "foo",
|
ID: "foo",
|
||||||
|
|
Loading…
Reference in New Issue