Merge pull request #1036 from hashicorp/b-serial-on-change
Only increment serial on state change
This commit is contained in:
commit
42eed99899
|
@ -19,7 +19,7 @@ type CacheState struct {
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *CacheState) State() *terraform.State {
|
func (s *CacheState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState will write and persist the state to the cache.
|
// WriteState will write and persist the state to the cache.
|
||||||
|
@ -104,6 +104,8 @@ func (s *CacheState) RefreshState() error {
|
||||||
s.refreshResult = CacheRefreshNoop
|
s.refreshResult = CacheRefreshNoop
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cached = durable
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = cached
|
s.state = cached
|
||||||
|
|
|
@ -10,7 +10,7 @@ type InmemState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) State() *terraform.State {
|
func (s *InmemState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) RefreshState() error {
|
func (s *InmemState) RefreshState() error {
|
||||||
|
@ -18,6 +18,7 @@ func (s *InmemState) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) WriteState(state *terraform.State) error {
|
func (s *InmemState) WriteState(state *terraform.State) error {
|
||||||
|
state.IncrementSerialMaybe(s.state)
|
||||||
s.state = state
|
s.state = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,20 @@ type LocalState struct {
|
||||||
Path string
|
Path string
|
||||||
PathOut string
|
PathOut string
|
||||||
|
|
||||||
state *terraform.State
|
state *terraform.State
|
||||||
written bool
|
readState *terraform.State
|
||||||
|
written bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetState will force a specific state in-memory for this local state.
|
// SetState will force a specific state in-memory for this local state.
|
||||||
func (s *LocalState) SetState(state *terraform.State) {
|
func (s *LocalState) SetState(state *terraform.State) {
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *LocalState) State() *terraform.State {
|
func (s *LocalState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState for LocalState always persists the state as well.
|
// WriteState for LocalState always persists the state as well.
|
||||||
|
@ -61,6 +63,9 @@ func (s *LocalState) WriteState(state *terraform.State) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
s.state.IncrementSerialMaybe(s.readState)
|
||||||
|
s.readState = s.state
|
||||||
|
|
||||||
if err := terraform.WriteState(s.state, f); err != nil {
|
if err := terraform.WriteState(s.state, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -105,5 +110,6 @@ func (s *LocalState) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,12 @@ import (
|
||||||
type State struct {
|
type State struct {
|
||||||
Client Client
|
Client Client
|
||||||
|
|
||||||
state *terraform.State
|
state, readState *terraform.State
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *State) State() *terraform.State {
|
func (s *State) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateWriter impl.
|
// StateWriter impl.
|
||||||
|
@ -43,11 +43,14 @@ func (s *State) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatePersister impl.
|
// StatePersister impl.
|
||||||
func (s *State) PersistState() error {
|
func (s *State) PersistState() error {
|
||||||
|
s.state.IncrementSerialMaybe(s.readState)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := terraform.WriteState(s.state, &buf); err != nil {
|
if err := terraform.WriteState(s.state, &buf); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,8 +7,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestState(t *testing.T) {
|
func TestState(t *testing.T) {
|
||||||
s := &State{Client: new(InmemClient)}
|
s := &State{
|
||||||
s.WriteState(state.TestStateInitial())
|
Client: new(InmemClient),
|
||||||
|
state: state.TestStateInitial(),
|
||||||
|
readState: state.TestStateInitial(),
|
||||||
|
}
|
||||||
if err := s.PersistState(); err != nil {
|
if err := s.PersistState(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
current := TestStateInitial()
|
current := TestStateInitial()
|
||||||
|
|
||||||
// Check that the initial state is correct
|
// Check that the initial state is correct
|
||||||
state := reader.State()
|
if state := reader.State(); !current.Equal(state) {
|
||||||
current.Serial = state.Serial
|
|
||||||
if !reflect.DeepEqual(state, current) {
|
|
||||||
t.Fatalf("not initial: %#v\n\n%#v", state, current)
|
t.Fatalf("not initial: %#v\n\n%#v", state, current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if actual := reader.State(); !reflect.DeepEqual(actual, current) {
|
if actual := reader.State(); !actual.Equal(current) {
|
||||||
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,11 +65,55 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
|
|
||||||
// Just set the serials the same... Then compare.
|
// Just set the serials the same... Then compare.
|
||||||
actual := reader.State()
|
actual := reader.State()
|
||||||
actual.Serial = current.Serial
|
if !actual.Equal(current) {
|
||||||
if !reflect.DeepEqual(actual, current) {
|
|
||||||
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can write and persist then verify that the serial
|
||||||
|
// is only implemented on change.
|
||||||
|
writer, writeOk := s.(StateWriter)
|
||||||
|
persister, persistOk := s.(StatePersister)
|
||||||
|
if writeOk && persistOk {
|
||||||
|
// Same serial
|
||||||
|
serial := current.Serial
|
||||||
|
if err := writer.WriteState(current); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := persister.PersistState(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader.State().Serial != serial {
|
||||||
|
t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the serial
|
||||||
|
currentCopy := *current
|
||||||
|
current = ¤tCopy
|
||||||
|
current.Modules = []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root", "somewhere"},
|
||||||
|
Outputs: map[string]string{"serialCheck": "true"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := writer.WriteState(current); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := persister.PersistState(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader.State().Serial <= serial {
|
||||||
|
t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that State() returns a copy
|
||||||
|
reader.State().Serial++
|
||||||
|
if reflect.DeepEqual(reader.State(), current) {
|
||||||
|
t.Fatal("State() should return a copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStateInitial is the initial state that a State should have
|
// TestStateInitial is the initial state that a State should have
|
||||||
|
|
|
@ -224,7 +224,7 @@ func (c *Context) Apply() (*State, error) {
|
||||||
defer c.releaseRun(v)
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
_, err := c.walk(walkApply)
|
_, err := c.walk(walkApply)
|
||||||
|
@ -264,7 +264,7 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
c.state = &State{}
|
c.state = &State{}
|
||||||
c.state.init()
|
c.state.init()
|
||||||
} else {
|
} else {
|
||||||
c.state = old.deepcopy()
|
c.state = old.DeepCopy()
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
c.state = old
|
c.state = old
|
||||||
|
@ -299,7 +299,7 @@ func (c *Context) Refresh() (*State, error) {
|
||||||
defer c.releaseRun(v)
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
if _, err := c.walk(walkRefresh); err != nil {
|
if _, err := c.walk(walkRefresh); err != nil {
|
||||||
|
|
|
@ -161,12 +161,20 @@ func (s *State) RootModule() *ModuleState {
|
||||||
|
|
||||||
// Equal tests if one state is equal to another.
|
// Equal tests if one state is equal to another.
|
||||||
func (s *State) Equal(other *State) bool {
|
func (s *State) Equal(other *State) bool {
|
||||||
|
// If one is nil, we do a direct check
|
||||||
|
if s == nil || other == nil {
|
||||||
|
return s == other
|
||||||
|
}
|
||||||
|
|
||||||
// If the versions are different, they're certainly not equal
|
// If the versions are different, they're certainly not equal
|
||||||
if s.Version != other.Version {
|
if s.Version != other.Version {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any of the modules are not equal, then this state isn't equal
|
// If any of the modules are not equal, then this state isn't equal
|
||||||
|
if len(s.Modules) != len(other.Modules) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, m := range s.Modules {
|
for _, m := range s.Modules {
|
||||||
// This isn't very optimal currently but works.
|
// This isn't very optimal currently but works.
|
||||||
otherM := other.ModuleByPath(m.Path)
|
otherM := other.ModuleByPath(m.Path)
|
||||||
|
@ -183,20 +191,9 @@ func (s *State) Equal(other *State) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) init() {
|
// DeepCopy performs a deep copy of the state structure and returns
|
||||||
if s.Version == 0 {
|
// a new structure.
|
||||||
s.Version = StateVersion
|
func (s *State) DeepCopy() *State {
|
||||||
}
|
|
||||||
if len(s.Modules) == 0 {
|
|
||||||
root := &ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
}
|
|
||||||
root.init()
|
|
||||||
s.Modules = []*ModuleState{root}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) deepcopy() *State {
|
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -214,6 +211,27 @@ func (s *State) deepcopy() *State {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncrementSerialMaybe increments the serial number of this state
|
||||||
|
// if it different from the other state.
|
||||||
|
func (s *State) IncrementSerialMaybe(other *State) {
|
||||||
|
if !s.Equal(other) {
|
||||||
|
s.Serial++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) init() {
|
||||||
|
if s.Version == 0 {
|
||||||
|
s.Version = StateVersion
|
||||||
|
}
|
||||||
|
if len(s.Modules) == 0 {
|
||||||
|
root := &ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
}
|
||||||
|
root.init()
|
||||||
|
s.Modules = []*ModuleState{root}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prune is used to remove any resources that are no longer required
|
// prune is used to remove any resources that are no longer required
|
||||||
func (s *State) prune() {
|
func (s *State) prune() {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -951,9 +969,6 @@ func WriteState(d *State, dst io.Writer) error {
|
||||||
// Ensure the version is set
|
// Ensure the version is set
|
||||||
d.Version = StateVersion
|
d.Version = StateVersion
|
||||||
|
|
||||||
// Always increment the serial number
|
|
||||||
d.Serial++
|
|
||||||
|
|
||||||
// Encode the data in a human-friendly way
|
// Encode the data in a human-friendly way
|
||||||
data, err := json.MarshalIndent(d, "", " ")
|
data, err := json.MarshalIndent(d, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -116,6 +116,19 @@ func TestStateEqual(t *testing.T) {
|
||||||
Result bool
|
Result bool
|
||||||
One, Two *State
|
One, Two *State
|
||||||
}{
|
}{
|
||||||
|
// Nils
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
&State{Version: 2},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
// Different versions
|
// Different versions
|
||||||
{
|
{
|
||||||
false,
|
false,
|
||||||
|
@ -159,6 +172,9 @@ func TestStateEqual(t *testing.T) {
|
||||||
if tc.One.Equal(tc.Two) != tc.Result {
|
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())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,15 +553,6 @@ func TestReadWriteState(t *testing.T) {
|
||||||
t.Fatalf("bad version number: %d", state.Version)
|
t.Fatalf("bad version number: %d", state.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the serial number is incremented
|
|
||||||
if state.Serial != 10 {
|
|
||||||
t.Fatalf("bad serial: %d", state.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the changes or the checksum will fail
|
|
||||||
state.Version = 0
|
|
||||||
state.Serial = 9
|
|
||||||
|
|
||||||
// Checksum after the write
|
// Checksum after the write
|
||||||
chksumAfter := checksumStruct(t, state)
|
chksumAfter := checksumStruct(t, state)
|
||||||
if chksumAfter != chksum {
|
if chksumAfter != chksum {
|
||||||
|
@ -557,10 +564,6 @@ func TestReadWriteState(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the changes came through
|
|
||||||
state.Version = StateVersion
|
|
||||||
state.Serial = 10
|
|
||||||
|
|
||||||
// ReadState should not restore sensitive information!
|
// ReadState should not restore sensitive information!
|
||||||
mod := state.RootModule()
|
mod := state.RootModule()
|
||||||
mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
|
mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
|
||||||
|
|
Loading…
Reference in New Issue