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 }