mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
32ae05c342
We can't check lineage in the remote state instance, because we may need to overwrite a state with a new lineage. Whil it's tempting to add an optional interface for this, like OverwriteState(), optional interfaces are never _really_ optional, and will have to be implemented by any wrapper types as well. Another solution may be to add a State.Supersedes field to indicate that we intend to replace an existing state, but that may not be worth the extra check either.
140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package remote
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/terraform/state"
|
|
"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 {
|
|
mu sync.Mutex
|
|
|
|
Client Client
|
|
|
|
state, readState *terraform.State
|
|
}
|
|
|
|
// StateReader impl.
|
|
func (s *State) State() *terraform.State {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
return s.state.DeepCopy()
|
|
}
|
|
|
|
// StateWriter impl.
|
|
func (s *State) WriteState(state *terraform.State) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.readState != nil && !state.SameLineage(s.readState) {
|
|
// This can't error here, because we need to be able to overwrite the
|
|
// state in some cases, like `state push -force` or `workspace new
|
|
// -state=`
|
|
log.Printf("[WARN] 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
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StateRefresher impl.
|
|
func (s *State) RefreshState() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
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
|
|
}
|
|
|
|
s.state = state
|
|
s.readState = s.state.DeepCopy() // our states must be separate instances so we can track changes
|
|
return nil
|
|
}
|
|
|
|
// StatePersister impl.
|
|
func (s *State) PersistState() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
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++
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := terraform.WriteState(s.state, &buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Lock calls the Client's Lock method if it's implemented.
|
|
func (s *State) Lock(info *state.LockInfo) (string, error) {
|
|
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 {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if c, ok := s.Client.(ClientLocker); ok {
|
|
return c.Unlock(id)
|
|
}
|
|
return nil
|
|
}
|