github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/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 }