github.com/nov1n/terraform@v0.7.9-0.20161103151050-bf6852f38e28/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 }