github.com/akazakov/terraform@v0.5.2-0.20160205142716-097441beafdf/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 }