github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/states/remote/state.go (about)

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