github.com/opsidian/terraform@v0.7.8-0.20161104123224-27c39cdfba5b/state/cache.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/terraform/terraform"
     7  )
     8  
     9  // CacheState is an implementation of the state interfaces that uses
    10  // a StateReadWriter for a local cache.
    11  type CacheState struct {
    12  	Cache   CacheStateCache
    13  	Durable CacheStateDurable
    14  
    15  	refreshResult CacheRefreshResult
    16  	state         *terraform.State
    17  }
    18  
    19  // StateReader impl.
    20  func (s *CacheState) State() *terraform.State {
    21  	return s.state.DeepCopy()
    22  }
    23  
    24  // WriteState will write and persist the state to the cache.
    25  //
    26  // StateWriter impl.
    27  func (s *CacheState) WriteState(state *terraform.State) error {
    28  	if err := s.Cache.WriteState(state); err != nil {
    29  		return err
    30  	}
    31  
    32  	s.state = state
    33  	return s.Cache.PersistState()
    34  }
    35  
    36  // RefreshState will refresh both the cache and the durable states. It
    37  // can return a myriad of errors (defined at the top of this file) depending
    38  // on potential conflicts that can occur while doing this.
    39  //
    40  // If the durable state is newer than the local cache, then the local cache
    41  // will be replaced with the durable.
    42  //
    43  // StateRefresher impl.
    44  func (s *CacheState) RefreshState() error {
    45  	// Refresh the durable state
    46  	if err := s.Durable.RefreshState(); err != nil {
    47  		return err
    48  	}
    49  
    50  	// Refresh the cached state
    51  	if err := s.Cache.RefreshState(); err != nil {
    52  		return err
    53  	}
    54  
    55  	// Handle the matrix of cases that can happen when comparing these
    56  	// two states.
    57  	cached := s.Cache.State()
    58  	durable := s.Durable.State()
    59  	switch {
    60  	case cached == nil && durable == nil:
    61  		// Initialized
    62  		s.refreshResult = CacheRefreshInit
    63  	case cached != nil && durable == nil:
    64  		// Cache is newer than remote. Not a big deal, user can just
    65  		// persist to get correct state.
    66  		s.refreshResult = CacheRefreshLocalNewer
    67  	case !cached.HasResources() && durable != nil:
    68  		// Cache should be updated since the remote is set but cache isn't
    69  		//
    70  		// If local is empty then we'll treat it as missing so that
    71  		// it can be overwritten by an already-existing remote. This
    72  		// allows the user to activate remote state for the first time
    73  		// against an already-existing remote state.
    74  		s.refreshResult = CacheRefreshUpdateLocal
    75  	case durable.Serial < cached.Serial:
    76  		// Cache is newer than remote. Not a big deal, user can just
    77  		// persist to get correct state.
    78  		s.refreshResult = CacheRefreshLocalNewer
    79  	case durable.Serial > cached.Serial:
    80  		// Cache should be updated since the remote is newer
    81  		s.refreshResult = CacheRefreshUpdateLocal
    82  	case durable.Serial == cached.Serial:
    83  		// They're supposedly equal, verify.
    84  		if cached.Equal(durable) {
    85  			// Hashes are the same, everything is great
    86  			s.refreshResult = CacheRefreshNoop
    87  			break
    88  		}
    89  
    90  		// This is very bad. This means we have two state files that
    91  		// have the same serial but have a different hash. We can't
    92  		// reconcile this. The most likely cause is parallel apply
    93  		// operations.
    94  		s.refreshResult = CacheRefreshConflict
    95  
    96  		// Return early so we don't updtae the state
    97  		return nil
    98  	default:
    99  		panic("unhandled cache refresh state")
   100  	}
   101  
   102  	if s.refreshResult == CacheRefreshUpdateLocal {
   103  		if err := s.Cache.WriteState(durable); err != nil {
   104  			s.refreshResult = CacheRefreshNoop
   105  			return err
   106  		}
   107  		if err := s.Cache.PersistState(); err != nil {
   108  			s.refreshResult = CacheRefreshNoop
   109  			return err
   110  		}
   111  
   112  		cached = durable
   113  	}
   114  
   115  	s.state = cached
   116  
   117  	return nil
   118  }
   119  
   120  // RefreshResult returns the result of the last refresh.
   121  func (s *CacheState) RefreshResult() CacheRefreshResult {
   122  	return s.refreshResult
   123  }
   124  
   125  // PersistState takes the local cache, assuming it is newer than the remote
   126  // state, and persists it to the durable storage. If you want to challenge the
   127  // assumption that the local state is the latest, call a RefreshState prior
   128  // to this.
   129  //
   130  // StatePersister impl.
   131  func (s *CacheState) PersistState() error {
   132  	if err := s.Durable.WriteState(s.state); err != nil {
   133  		return err
   134  	}
   135  
   136  	return s.Durable.PersistState()
   137  }
   138  
   139  // CacheStateCache is the meta-interface that must be implemented for
   140  // the cache for the CacheState.
   141  type CacheStateCache interface {
   142  	StateReader
   143  	StateWriter
   144  	StatePersister
   145  	StateRefresher
   146  }
   147  
   148  // CacheStateDurable is the meta-interface that must be implemented for
   149  // the durable storage for CacheState.
   150  type CacheStateDurable interface {
   151  	StateReader
   152  	StateWriter
   153  	StatePersister
   154  	StateRefresher
   155  }
   156  
   157  // CacheRefreshResult is used to explain the result of the previous
   158  // RefreshState for a CacheState.
   159  type CacheRefreshResult int
   160  
   161  const (
   162  	// CacheRefreshNoop indicates nothing has happened,
   163  	// but that does not indicate an error. Everything is
   164  	// just up to date. (Push/Pull)
   165  	CacheRefreshNoop CacheRefreshResult = iota
   166  
   167  	// CacheRefreshInit indicates that there is no local or
   168  	// remote state, and that the state was initialized
   169  	CacheRefreshInit
   170  
   171  	// CacheRefreshUpdateLocal indicates the local state
   172  	// was updated. (Pull)
   173  	CacheRefreshUpdateLocal
   174  
   175  	// CacheRefreshUpdateRemote indicates the remote state
   176  	// was updated. (Push)
   177  	CacheRefreshUpdateRemote
   178  
   179  	// CacheRefreshLocalNewer means the pull was a no-op
   180  	// because the local state is newer than that of the
   181  	// server. This means a Push should take place. (Pull)
   182  	CacheRefreshLocalNewer
   183  
   184  	// CacheRefreshRemoteNewer means the push was a no-op
   185  	// because the remote state is newer than that of the
   186  	// local state. This means a Pull should take place.
   187  	// (Push)
   188  	CacheRefreshRemoteNewer
   189  
   190  	// CacheRefreshConflict means that the push or pull
   191  	// was a no-op because there is a conflict. This means
   192  	// there are multiple state definitions at the same
   193  	// serial number with different contents. This requires
   194  	// an operator to intervene and resolve the conflict.
   195  	// Shame on the user for doing concurrent apply.
   196  	// (Push/Pull)
   197  	CacheRefreshConflict
   198  )
   199  
   200  func (sc CacheRefreshResult) String() string {
   201  	switch sc {
   202  	case CacheRefreshNoop:
   203  		return "Local and remote state in sync"
   204  	case CacheRefreshInit:
   205  		return "Local state initialized"
   206  	case CacheRefreshUpdateLocal:
   207  		return "Local state updated"
   208  	case CacheRefreshUpdateRemote:
   209  		return "Remote state updated"
   210  	case CacheRefreshLocalNewer:
   211  		return "Local state is newer than remote state, push required"
   212  	case CacheRefreshRemoteNewer:
   213  		return "Remote state is newer than local state, pull required"
   214  	case CacheRefreshConflict:
   215  		return "Local and remote state conflict, manual resolution required"
   216  	default:
   217  		return fmt.Sprintf("Unknown state change type: %d", sc)
   218  	}
   219  }
   220  
   221  // SuccessfulPull is used to clasify the CacheRefreshResult for
   222  // a refresh operation. This is different by operation, but can be used
   223  // to determine a proper exit code.
   224  func (sc CacheRefreshResult) SuccessfulPull() bool {
   225  	switch sc {
   226  	case CacheRefreshNoop:
   227  		return true
   228  	case CacheRefreshInit:
   229  		return true
   230  	case CacheRefreshUpdateLocal:
   231  		return true
   232  	case CacheRefreshLocalNewer:
   233  		return false
   234  	case CacheRefreshConflict:
   235  		return false
   236  	default:
   237  		return false
   238  	}
   239  }