github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/state/cache.go (about)

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