github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/remote/state.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package remote
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"log"
    10  	"sync"
    11  
    12  	uuid "github.com/hashicorp/go-uuid"
    13  
    14  	"github.com/terramate-io/tf/backend/local"
    15  	"github.com/terramate-io/tf/states"
    16  	"github.com/terramate-io/tf/states/statefile"
    17  	"github.com/terramate-io/tf/states/statemgr"
    18  	"github.com/terramate-io/tf/terraform"
    19  )
    20  
    21  // State implements the State interfaces in the state package to handle
    22  // reading and writing the remote state. This State on its own does no
    23  // local caching so every persist will go to the remote storage and local
    24  // writes will go to memory.
    25  type State struct {
    26  	mu sync.Mutex
    27  
    28  	Client Client
    29  
    30  	// We track two pieces of meta data in addition to the state itself:
    31  	//
    32  	// lineage - the state's unique ID
    33  	// serial  - the monotonic counter of "versions" of the state
    34  	//
    35  	// Both of these (along with state) have a sister field
    36  	// that represents the values read in from an existing source.
    37  	// All three of these values are used to determine if the new
    38  	// state has changed from an existing state we read in.
    39  	lineage, readLineage string
    40  	serial, readSerial   uint64
    41  	state, readState     *states.State
    42  	disableLocks         bool
    43  
    44  	// If this is set then the state manager will decline to store intermediate
    45  	// state snapshots created while a Terraform Core apply operation is in
    46  	// progress. Otherwise (by default) it will accept persistent snapshots
    47  	// using the default rules defined in the local backend.
    48  	DisableIntermediateSnapshots bool
    49  }
    50  
    51  var _ statemgr.Full = (*State)(nil)
    52  var _ statemgr.Migrator = (*State)(nil)
    53  var _ local.IntermediateStateConditionalPersister = (*State)(nil)
    54  
    55  // statemgr.Reader impl.
    56  func (s *State) State() *states.State {
    57  	s.mu.Lock()
    58  	defer s.mu.Unlock()
    59  
    60  	return s.state.DeepCopy()
    61  }
    62  
    63  func (s *State) GetRootOutputValues() (map[string]*states.OutputValue, error) {
    64  	if err := s.RefreshState(); err != nil {
    65  		return nil, fmt.Errorf("Failed to load state: %s", err)
    66  	}
    67  
    68  	state := s.State()
    69  	if state == nil {
    70  		state = states.NewState()
    71  	}
    72  
    73  	return state.RootModule().OutputValues, nil
    74  }
    75  
    76  // StateForMigration is part of our implementation of statemgr.Migrator.
    77  func (s *State) StateForMigration() *statefile.File {
    78  	s.mu.Lock()
    79  	defer s.mu.Unlock()
    80  
    81  	return statefile.New(s.state.DeepCopy(), s.lineage, s.serial)
    82  }
    83  
    84  // statemgr.Writer impl.
    85  func (s *State) WriteState(state *states.State) error {
    86  	s.mu.Lock()
    87  	defer s.mu.Unlock()
    88  
    89  	// We create a deep copy of the state here, because the caller also has
    90  	// a reference to the given object and can potentially go on to mutate
    91  	// it after we return, but we want the snapshot at this point in time.
    92  	s.state = state.DeepCopy()
    93  
    94  	return nil
    95  }
    96  
    97  // WriteStateForMigration is part of our implementation of statemgr.Migrator.
    98  func (s *State) WriteStateForMigration(f *statefile.File, force bool) error {
    99  	s.mu.Lock()
   100  	defer s.mu.Unlock()
   101  
   102  	if !force {
   103  		checkFile := statefile.New(s.state, s.lineage, s.serial)
   104  		if err := statemgr.CheckValidImport(f, checkFile); err != nil {
   105  			return err
   106  		}
   107  	}
   108  
   109  	// The remote backend needs to pass the `force` flag through to its client.
   110  	// For backends that support such operations, inform the client
   111  	// that a force push has been requested
   112  	c, isForcePusher := s.Client.(ClientForcePusher)
   113  	if force && isForcePusher {
   114  		c.EnableForcePush()
   115  	}
   116  
   117  	// We create a deep copy of the state here, because the caller also has
   118  	// a reference to the given object and can potentially go on to mutate
   119  	// it after we return, but we want the snapshot at this point in time.
   120  	s.state = f.State.DeepCopy()
   121  	s.lineage = f.Lineage
   122  	s.serial = f.Serial
   123  
   124  	return nil
   125  }
   126  
   127  // statemgr.Refresher impl.
   128  func (s *State) RefreshState() error {
   129  	s.mu.Lock()
   130  	defer s.mu.Unlock()
   131  	return s.refreshState()
   132  }
   133  
   134  // refreshState is the main implementation of RefreshState, but split out so
   135  // that we can make internal calls to it from methods that are already holding
   136  // the s.mu lock.
   137  func (s *State) refreshState() error {
   138  	payload, err := s.Client.Get()
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	// no remote state is OK
   144  	if payload == nil {
   145  		s.readState = nil
   146  		s.lineage = ""
   147  		s.serial = 0
   148  		return nil
   149  	}
   150  
   151  	stateFile, err := statefile.Read(bytes.NewReader(payload.Data))
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	s.lineage = stateFile.Lineage
   157  	s.serial = stateFile.Serial
   158  	s.state = stateFile.State
   159  
   160  	// Properties from the remote must be separate so we can
   161  	// track changes as lineage, serial and/or state are mutated
   162  	s.readLineage = stateFile.Lineage
   163  	s.readSerial = stateFile.Serial
   164  	s.readState = s.state.DeepCopy()
   165  	return nil
   166  }
   167  
   168  // statemgr.Persister impl.
   169  func (s *State) PersistState(schemas *terraform.Schemas) error {
   170  	s.mu.Lock()
   171  	defer s.mu.Unlock()
   172  
   173  	log.Printf("[DEBUG] states/remote: state read serial is: %d; serial is: %d", s.readSerial, s.serial)
   174  	log.Printf("[DEBUG] states/remote: state read lineage is: %s; lineage is: %s", s.readLineage, s.lineage)
   175  
   176  	if s.readState != nil {
   177  		lineageUnchanged := s.readLineage != "" && s.lineage == s.readLineage
   178  		serialUnchanged := s.readSerial != 0 && s.serial == s.readSerial
   179  		stateUnchanged := statefile.StatesMarshalEqual(s.state, s.readState)
   180  		if stateUnchanged && lineageUnchanged && serialUnchanged {
   181  			// If the state, lineage or serial haven't changed at all then we have nothing to do.
   182  			return nil
   183  		}
   184  		s.serial++
   185  	} else {
   186  		// We might be writing a new state altogether, but before we do that
   187  		// we'll check to make sure there isn't already a snapshot present
   188  		// that we ought to be updating.
   189  		err := s.refreshState()
   190  		if err != nil {
   191  			return fmt.Errorf("failed checking for existing remote state: %s", err)
   192  		}
   193  		log.Printf("[DEBUG] states/remote: after refresh, state read serial is: %d; serial is: %d", s.readSerial, s.serial)
   194  		log.Printf("[DEBUG] states/remote: after refresh, state read lineage is: %s; lineage is: %s", s.readLineage, s.lineage)
   195  		if s.lineage == "" { // indicates that no state snapshot is present yet
   196  			lineage, err := uuid.GenerateUUID()
   197  			if err != nil {
   198  				return fmt.Errorf("failed to generate initial lineage: %v", err)
   199  			}
   200  			s.lineage = lineage
   201  			s.serial++
   202  		}
   203  	}
   204  
   205  	f := statefile.New(s.state, s.lineage, s.serial)
   206  
   207  	var buf bytes.Buffer
   208  	err := statefile.Write(f, &buf)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	err = s.Client.Put(buf.Bytes())
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	// After we've successfully persisted, what we just wrote is our new
   219  	// reference state until someone calls RefreshState again.
   220  	// We've potentially overwritten (via force) the state, lineage
   221  	// and / or serial (and serial was incremented) so we copy over all
   222  	// three fields so everything matches the new state and a subsequent
   223  	// operation would correctly detect no changes to the lineage, serial or state.
   224  	s.readState = s.state.DeepCopy()
   225  	s.readLineage = s.lineage
   226  	s.readSerial = s.serial
   227  	return nil
   228  }
   229  
   230  // ShouldPersistIntermediateState implements local.IntermediateStateConditionalPersister
   231  func (s *State) ShouldPersistIntermediateState(info *local.IntermediateStatePersistInfo) bool {
   232  	if s.DisableIntermediateSnapshots {
   233  		return false
   234  	}
   235  	return local.DefaultIntermediateStatePersistRule(info)
   236  }
   237  
   238  // Lock calls the Client's Lock method if it's implemented.
   239  func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
   240  	s.mu.Lock()
   241  	defer s.mu.Unlock()
   242  
   243  	if s.disableLocks {
   244  		return "", nil
   245  	}
   246  
   247  	if c, ok := s.Client.(ClientLocker); ok {
   248  		return c.Lock(info)
   249  	}
   250  	return "", nil
   251  }
   252  
   253  // Unlock calls the Client's Unlock method if it's implemented.
   254  func (s *State) Unlock(id string) error {
   255  	s.mu.Lock()
   256  	defer s.mu.Unlock()
   257  
   258  	if s.disableLocks {
   259  		return nil
   260  	}
   261  
   262  	if c, ok := s.Client.(ClientLocker); ok {
   263  		return c.Unlock(id)
   264  	}
   265  	return nil
   266  }
   267  
   268  // DisableLocks turns the Lock and Unlock methods into no-ops. This is intended
   269  // to be called during initialization of a state manager and should not be
   270  // called after any of the statemgr.Full interface methods have been called.
   271  func (s *State) DisableLocks() {
   272  	s.disableLocks = true
   273  }
   274  
   275  // StateSnapshotMeta returns the metadata from the most recently persisted
   276  // or refreshed persistent state snapshot.
   277  //
   278  // This is an implementation of statemgr.PersistentMeta.
   279  func (s *State) StateSnapshotMeta() statemgr.SnapshotMeta {
   280  	return statemgr.SnapshotMeta{
   281  		Lineage: s.lineage,
   282  		Serial:  s.serial,
   283  	}
   284  }