terraform/state/remote/state.go

137 lines
3.3 KiB
Go
Raw Normal View History

2015-02-21 20:52:55 +01:00
package remote
import (
"bytes"
state: more robust handling of state Serial Previously we relied on a constellation of coincidences for everything to work out correctly with state serials. In particular, callers needed to be very careful about mutating states (or not) because many different bits of code shared pointers to the same objects. Here we move to a model where all of the state managers always use distinct instances of state, copied when WriteState is called. This means that they are truly a snapshot of the state as it was at that call, even if the caller goes on mutating the state that was passed. We also adjust the handling of serials so that the state managers ignore any serials in incoming states and instead just treat each Persist as the next version after what was most recently Refreshed. (An exception exists for when nothing has been refreshed, e.g. because we are writing a state to a location for the first time. In that case we _do_ trust the caller, since the given state is either a new state or it's a copy of something we're migrating from elsewhere with its state and lineage intact.) The intent here is to allow the rest of Terraform to not worry about serials and state identity, and instead just treat the state as a mutable structure. We'll just snapshot it occasionally, when WriteState is called, and deal with serials _only_ at persist time. This is intended as a more robust version of #15423, which was a quick hotfix to an issue that resulted from our previous slopping handling of state serials but arguably makes the problem worse by depending on an additional coincidental behavior of the local backend's apply implementation.
2017-07-05 21:34:30 +02:00
"fmt"
2017-05-25 17:01:25 +02:00
"sync"
2015-02-21 20:52:55 +01:00
"github.com/hashicorp/terraform/state"
2015-02-21 20:52:55 +01:00
"github.com/hashicorp/terraform/terraform"
)
// State implements the State interfaces in the state package to handle
// reading and writing the remote state. This State on its own does no
// local caching so every persist will go to the remote storage and local
// writes will go to memory.
type State struct {
2017-05-25 17:01:25 +02:00
mu sync.Mutex
2015-02-21 20:52:55 +01:00
Client Client
state, readState *terraform.State
2015-02-21 20:52:55 +01:00
}
// StateReader impl.
func (s *State) State() *terraform.State {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
2015-02-24 06:36:35 +01:00
return s.state.DeepCopy()
2015-02-21 20:52:55 +01:00
}
// StateWriter impl.
func (s *State) WriteState(state *terraform.State) error {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
state: more robust handling of state Serial Previously we relied on a constellation of coincidences for everything to work out correctly with state serials. In particular, callers needed to be very careful about mutating states (or not) because many different bits of code shared pointers to the same objects. Here we move to a model where all of the state managers always use distinct instances of state, copied when WriteState is called. This means that they are truly a snapshot of the state as it was at that call, even if the caller goes on mutating the state that was passed. We also adjust the handling of serials so that the state managers ignore any serials in incoming states and instead just treat each Persist as the next version after what was most recently Refreshed. (An exception exists for when nothing has been refreshed, e.g. because we are writing a state to a location for the first time. In that case we _do_ trust the caller, since the given state is either a new state or it's a copy of something we're migrating from elsewhere with its state and lineage intact.) The intent here is to allow the rest of Terraform to not worry about serials and state identity, and instead just treat the state as a mutable structure. We'll just snapshot it occasionally, when WriteState is called, and deal with serials _only_ at persist time. This is intended as a more robust version of #15423, which was a quick hotfix to an issue that resulted from our previous slopping handling of state serials but arguably makes the problem worse by depending on an additional coincidental behavior of the local backend's apply implementation.
2017-07-05 21:34:30 +02:00
if s.readState != nil && !state.SameLineage(s.readState) {
return fmt.Errorf("incompatible state lineage; given %s but want %s", state.Lineage, s.readState.Lineage)
}
// We create a deep copy of the state here, because the caller also has
// a reference to the given object and can potentially go on to mutate
// it after we return, but we want the snapshot at this point in time.
s.state = state.DeepCopy()
// Force our new state to have the same serial as our read state. We'll
// update this if PersistState is called later. (We don't require nor trust
// the caller to properly maintain serial for transient state objects since
// the rest of Terraform treats state as an openly mutable object.)
//
// If we have no read state then we assume we're either writing a new
// state for the first time or we're migrating a state from elsewhere,
// and in both cases we wish to retain the lineage and serial from
// the given state.
if s.readState != nil {
s.state.Serial = s.readState.Serial
}
2015-02-21 20:52:55 +01:00
return nil
}
// StateRefresher impl.
func (s *State) RefreshState() error {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
2015-02-21 20:52:55 +01:00
payload, err := s.Client.Get()
if err != nil {
return err
}
// no remote state is OK
if payload == nil {
return nil
}
state, err := terraform.ReadState(bytes.NewReader(payload.Data))
if err != nil {
return err
2015-02-21 20:52:55 +01:00
}
s.state = state
state: more robust handling of state Serial Previously we relied on a constellation of coincidences for everything to work out correctly with state serials. In particular, callers needed to be very careful about mutating states (or not) because many different bits of code shared pointers to the same objects. Here we move to a model where all of the state managers always use distinct instances of state, copied when WriteState is called. This means that they are truly a snapshot of the state as it was at that call, even if the caller goes on mutating the state that was passed. We also adjust the handling of serials so that the state managers ignore any serials in incoming states and instead just treat each Persist as the next version after what was most recently Refreshed. (An exception exists for when nothing has been refreshed, e.g. because we are writing a state to a location for the first time. In that case we _do_ trust the caller, since the given state is either a new state or it's a copy of something we're migrating from elsewhere with its state and lineage intact.) The intent here is to allow the rest of Terraform to not worry about serials and state identity, and instead just treat the state as a mutable structure. We'll just snapshot it occasionally, when WriteState is called, and deal with serials _only_ at persist time. This is intended as a more robust version of #15423, which was a quick hotfix to an issue that resulted from our previous slopping handling of state serials but arguably makes the problem worse by depending on an additional coincidental behavior of the local backend's apply implementation.
2017-07-05 21:34:30 +02:00
s.readState = s.state.DeepCopy() // our states must be separate instances so we can track changes
2015-02-21 20:52:55 +01:00
return nil
}
// StatePersister impl.
func (s *State) PersistState() error {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
state: more robust handling of state Serial Previously we relied on a constellation of coincidences for everything to work out correctly with state serials. In particular, callers needed to be very careful about mutating states (or not) because many different bits of code shared pointers to the same objects. Here we move to a model where all of the state managers always use distinct instances of state, copied when WriteState is called. This means that they are truly a snapshot of the state as it was at that call, even if the caller goes on mutating the state that was passed. We also adjust the handling of serials so that the state managers ignore any serials in incoming states and instead just treat each Persist as the next version after what was most recently Refreshed. (An exception exists for when nothing has been refreshed, e.g. because we are writing a state to a location for the first time. In that case we _do_ trust the caller, since the given state is either a new state or it's a copy of something we're migrating from elsewhere with its state and lineage intact.) The intent here is to allow the rest of Terraform to not worry about serials and state identity, and instead just treat the state as a mutable structure. We'll just snapshot it occasionally, when WriteState is called, and deal with serials _only_ at persist time. This is intended as a more robust version of #15423, which was a quick hotfix to an issue that resulted from our previous slopping handling of state serials but arguably makes the problem worse by depending on an additional coincidental behavior of the local backend's apply implementation.
2017-07-05 21:34:30 +02:00
if !s.state.MarshalEqual(s.readState) {
// Our new state does not marshal as byte-for-byte identical to
// the old, so we need to increment the serial.
// Note that in WriteState we force the serial to match that of
// s.readState, if we have a readState.
s.state.Serial++
}
2015-02-21 20:52:55 +01:00
var buf bytes.Buffer
if err := terraform.WriteState(s.state, &buf); err != nil {
return err
}
state: more robust handling of state Serial Previously we relied on a constellation of coincidences for everything to work out correctly with state serials. In particular, callers needed to be very careful about mutating states (or not) because many different bits of code shared pointers to the same objects. Here we move to a model where all of the state managers always use distinct instances of state, copied when WriteState is called. This means that they are truly a snapshot of the state as it was at that call, even if the caller goes on mutating the state that was passed. We also adjust the handling of serials so that the state managers ignore any serials in incoming states and instead just treat each Persist as the next version after what was most recently Refreshed. (An exception exists for when nothing has been refreshed, e.g. because we are writing a state to a location for the first time. In that case we _do_ trust the caller, since the given state is either a new state or it's a copy of something we're migrating from elsewhere with its state and lineage intact.) The intent here is to allow the rest of Terraform to not worry about serials and state identity, and instead just treat the state as a mutable structure. We'll just snapshot it occasionally, when WriteState is called, and deal with serials _only_ at persist time. This is intended as a more robust version of #15423, which was a quick hotfix to an issue that resulted from our previous slopping handling of state serials but arguably makes the problem worse by depending on an additional coincidental behavior of the local backend's apply implementation.
2017-07-05 21:34:30 +02:00
err := s.Client.Put(buf.Bytes())
if err != nil {
return err
}
// After we've successfully persisted, what we just wrote is our new
// reference state until someone calls RefreshState again.
s.readState = s.state.DeepCopy()
return nil
2015-02-21 20:52:55 +01:00
}
// Lock calls the Client's Lock method if it's implemented.
func (s *State) Lock(info *state.LockInfo) (string, error) {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
if c, ok := s.Client.(ClientLocker); ok {
return c.Lock(info)
}
return "", nil
}
// Unlock calls the Client's Unlock method if it's implemented.
func (s *State) Unlock(id string) error {
2017-05-25 17:01:25 +02:00
s.mu.Lock()
defer s.mu.Unlock()
if c, ok := s.Client.(ClientLocker); ok {
return c.Unlock(id)
}
return nil
}