github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/state/cache.go (about)

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