github.com/tam7t/terraform@v0.7.0-rc2.0.20160705125922-be2469a05c5e/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 == nil && durable != nil:
    68  		// Cache should be updated since the remote is set but cache isn't
    69  		s.refreshResult = CacheRefreshUpdateLocal
    70  	case durable.Serial < cached.Serial:
    71  		// Cache is newer than remote. Not a big deal, user can just
    72  		// persist to get correct state.
    73  		s.refreshResult = CacheRefreshLocalNewer
    74  	case durable.Serial > cached.Serial:
    75  		// Cache should be updated since the remote is newer
    76  		s.refreshResult = CacheRefreshUpdateLocal
    77  	case durable.Serial == cached.Serial:
    78  		// They're supposedly equal, verify.
    79  		if cached.Equal(durable) {
    80  			// Hashes are the same, everything is great
    81  			s.refreshResult = CacheRefreshNoop
    82  			break
    83  		}
    84  
    85  		// This is very bad. This means we have two state files that
    86  		// have the same serial but have a different hash. We can't
    87  		// reconcile this. The most likely cause is parallel apply
    88  		// operations.
    89  		s.refreshResult = CacheRefreshConflict
    90  
    91  		// Return early so we don't updtae the state
    92  		return nil
    93  	default:
    94  		panic("unhandled cache refresh state")
    95  	}
    96  
    97  	if s.refreshResult == CacheRefreshUpdateLocal {
    98  		if err := s.Cache.WriteState(durable); err != nil {
    99  			s.refreshResult = CacheRefreshNoop
   100  			return err
   101  		}
   102  		if err := s.Cache.PersistState(); err != nil {
   103  			s.refreshResult = CacheRefreshNoop
   104  			return err
   105  		}
   106  
   107  		cached = durable
   108  	}
   109  
   110  	s.state = cached
   111  
   112  	return nil
   113  }
   114  
   115  // RefreshResult returns the result of the last refresh.
   116  func (s *CacheState) RefreshResult() CacheRefreshResult {
   117  	return s.refreshResult
   118  }
   119  
   120  // PersistState takes the local cache, assuming it is newer than the remote
   121  // state, and persists it to the durable storage. If you want to challenge the
   122  // assumption that the local state is the latest, call a RefreshState prior
   123  // to this.
   124  //
   125  // StatePersister impl.
   126  func (s *CacheState) PersistState() error {
   127  	if err := s.Durable.WriteState(s.state); err != nil {
   128  		return err
   129  	}
   130  
   131  	return s.Durable.PersistState()
   132  }
   133  
   134  // CacheStateCache is the meta-interface that must be implemented for
   135  // the cache for the CacheState.
   136  type CacheStateCache interface {
   137  	StateReader
   138  	StateWriter
   139  	StatePersister
   140  	StateRefresher
   141  }
   142  
   143  // CacheStateDurable is the meta-interface that must be implemented for
   144  // the durable storage for CacheState.
   145  type CacheStateDurable interface {
   146  	StateReader
   147  	StateWriter
   148  	StatePersister
   149  	StateRefresher
   150  }
   151  
   152  // CacheRefreshResult is used to explain the result of the previous
   153  // RefreshState for a CacheState.
   154  type CacheRefreshResult int
   155  
   156  const (
   157  	// CacheRefreshNoop indicates nothing has happened,
   158  	// but that does not indicate an error. Everything is
   159  	// just up to date. (Push/Pull)
   160  	CacheRefreshNoop CacheRefreshResult = iota
   161  
   162  	// CacheRefreshInit indicates that there is no local or
   163  	// remote state, and that the state was initialized
   164  	CacheRefreshInit
   165  
   166  	// CacheRefreshUpdateLocal indicates the local state
   167  	// was updated. (Pull)
   168  	CacheRefreshUpdateLocal
   169  
   170  	// CacheRefreshUpdateRemote indicates the remote state
   171  	// was updated. (Push)
   172  	CacheRefreshUpdateRemote
   173  
   174  	// CacheRefreshLocalNewer means the pull was a no-op
   175  	// because the local state is newer than that of the
   176  	// server. This means a Push should take place. (Pull)
   177  	CacheRefreshLocalNewer
   178  
   179  	// CacheRefreshRemoteNewer means the push was a no-op
   180  	// because the remote state is newer than that of the
   181  	// local state. This means a Pull should take place.
   182  	// (Push)
   183  	CacheRefreshRemoteNewer
   184  
   185  	// CacheRefreshConflict means that the push or pull
   186  	// was a no-op because there is a conflict. This means
   187  	// there are multiple state definitions at the same
   188  	// serial number with different contents. This requires
   189  	// an operator to intervene and resolve the conflict.
   190  	// Shame on the user for doing concurrent apply.
   191  	// (Push/Pull)
   192  	CacheRefreshConflict
   193  )
   194  
   195  func (sc CacheRefreshResult) String() string {
   196  	switch sc {
   197  	case CacheRefreshNoop:
   198  		return "Local and remote state in sync"
   199  	case CacheRefreshInit:
   200  		return "Local state initialized"
   201  	case CacheRefreshUpdateLocal:
   202  		return "Local state updated"
   203  	case CacheRefreshUpdateRemote:
   204  		return "Remote state updated"
   205  	case CacheRefreshLocalNewer:
   206  		return "Local state is newer than remote state, push required"
   207  	case CacheRefreshRemoteNewer:
   208  		return "Remote state is newer than local state, pull required"
   209  	case CacheRefreshConflict:
   210  		return "Local and remote state conflict, manual resolution required"
   211  	default:
   212  		return fmt.Sprintf("Unknown state change type: %d", sc)
   213  	}
   214  }
   215  
   216  // SuccessfulPull is used to clasify the CacheRefreshResult for
   217  // a refresh operation. This is different by operation, but can be used
   218  // to determine a proper exit code.
   219  func (sc CacheRefreshResult) SuccessfulPull() bool {
   220  	switch sc {
   221  	case CacheRefreshNoop:
   222  		return true
   223  	case CacheRefreshInit:
   224  		return true
   225  	case CacheRefreshUpdateLocal:
   226  		return true
   227  	case CacheRefreshLocalNewer:
   228  		return false
   229  	case CacheRefreshConflict:
   230  		return false
   231  	default:
   232  		return false
   233  	}
   234  }