github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/command/state.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/hashicorp/errwrap" 9 "github.com/hashicorp/terraform/state" 10 "github.com/hashicorp/terraform/state/remote" 11 "github.com/hashicorp/terraform/terraform" 12 ) 13 14 // StateOpts are options to get the state for a command. 15 type StateOpts struct { 16 // LocalPath is the path where the state is stored locally. 17 // 18 // LocalPathOut is the path where the local state will be saved. If this 19 // isn't set, it will be saved back to LocalPath. 20 LocalPath string 21 LocalPathOut string 22 23 // RemotePath is the path where the remote state cache would be. 24 // 25 // RemoteCache, if true, will set the result to only be the cache 26 // and not backed by any real durable storage. 27 RemotePath string 28 RemoteCacheOnly bool 29 RemoteRefresh bool 30 31 // BackupPath is the path where the backup will be placed. If not set, 32 // it is assumed to be the path where the state is stored locally 33 // plus the DefaultBackupExtension. 34 BackupPath string 35 36 // ForceState is a state structure to force the value to be. This 37 // is used by Terraform plans (which contain their state). 38 ForceState *terraform.State 39 } 40 41 // StateResult is the result of calling State and holds various different 42 // State implementations so they can be accessed directly. 43 type StateResult struct { 44 // State is the final outer state that should be used for all 45 // _real_ reads/writes. 46 // 47 // StatePath is the local path where the state will be stored or 48 // cached, no matter whether State is local or remote. 49 State state.State 50 StatePath string 51 52 // Local and Remote are the local/remote state implementations, raw 53 // and unwrapped by any backups. The paths here are the paths where 54 // these state files would be saved. 55 Local *state.LocalState 56 LocalPath string 57 Remote *state.CacheState 58 RemotePath string 59 } 60 61 // State returns the proper state.State implementation to represent the 62 // current environment. 63 // 64 // localPath is the path to where state would be if stored locally. 65 // dataDir is the path to the local data directory where the remote state 66 // cache would be stored. 67 func State(opts *StateOpts) (*StateResult, error) { 68 result := new(StateResult) 69 70 // Get the remote state cache path 71 if opts.RemotePath != "" { 72 result.RemotePath = opts.RemotePath 73 74 var remote *state.CacheState 75 if opts.RemoteCacheOnly { 76 // Setup the in-memory state 77 ls := &state.LocalState{Path: opts.RemotePath} 78 if err := ls.RefreshState(); err != nil { 79 return nil, err 80 } 81 82 // If we have a forced state, set it 83 if opts.ForceState != nil { 84 ls.SetState(opts.ForceState) 85 } 86 87 is := &state.InmemState{} 88 is.WriteState(ls.State()) 89 90 // Setupt he remote state, cache-only, and refresh it so that 91 // we have access to the state right away. 92 remote = &state.CacheState{ 93 Cache: ls, 94 Durable: is, 95 } 96 if err := remote.RefreshState(); err != nil { 97 return nil, err 98 } 99 } else { 100 // If we have a forced state that is remote, then we load that 101 if opts.ForceState != nil && 102 opts.ForceState.Remote != nil && 103 opts.ForceState.Remote.Type != "" { 104 var err error 105 remote, err = remoteState( 106 opts.ForceState, 107 opts.RemotePath, 108 false) 109 if err != nil { 110 return nil, err 111 } 112 } else { 113 // Only if we have no forced state, we check our normal 114 // remote path. 115 if _, err := os.Stat(opts.RemotePath); err == nil { 116 // We have a remote state, initialize that. 117 remote, err = remoteStateFromPath( 118 opts.RemotePath, 119 opts.RemoteRefresh) 120 if err != nil { 121 return nil, err 122 } 123 } 124 } 125 } 126 127 if remote != nil { 128 result.State = remote 129 result.StatePath = opts.RemotePath 130 result.Remote = remote 131 } 132 } 133 134 // If we have a forced state and we were able to initialize that 135 // into a remote state, we don't do any local state stuff. This is 136 // because normally we're able to test whether we should do local vs. 137 // remote by checking file existence. With ForceState, file existence 138 // doesn't work because neither may exist, so we use state attributes. 139 if opts.ForceState != nil && result.Remote != nil { 140 opts.LocalPath = "" 141 } 142 143 // Do we have a local state? 144 if opts.LocalPath != "" { 145 local := &state.LocalState{ 146 Path: opts.LocalPath, 147 PathOut: opts.LocalPathOut, 148 } 149 150 // Always store it in the result even if we're not using it 151 result.Local = local 152 result.LocalPath = local.Path 153 if local.PathOut != "" { 154 result.LocalPath = local.PathOut 155 } 156 157 // If we're forcing, then set it 158 if opts.ForceState != nil { 159 local.SetState(opts.ForceState) 160 } else { 161 // If we're not forcing, then we load the state directly 162 // from disk. 163 err := local.RefreshState() 164 if err == nil { 165 if result.State != nil && !result.State.State().Empty() { 166 if !local.State().Empty() { 167 // We already have a remote state... that is an error. 168 return nil, fmt.Errorf( 169 "Remote state found, but state file '%s' also present.", 170 opts.LocalPath) 171 } 172 173 // Empty state 174 local = nil 175 } 176 } 177 if err != nil { 178 return nil, errwrap.Wrapf( 179 "Error reading local state: {{err}}", err) 180 } 181 } 182 183 if local != nil { 184 result.State = local 185 result.StatePath = opts.LocalPath 186 if opts.LocalPathOut != "" { 187 result.StatePath = opts.LocalPathOut 188 } 189 } 190 } 191 192 // If we have a result, make sure to back it up 193 if result.State != nil { 194 backupPath := result.StatePath + DefaultBackupExtension 195 if opts.BackupPath != "" { 196 backupPath = opts.BackupPath 197 } 198 199 if backupPath != "-" { 200 result.State = &state.BackupState{ 201 Real: result.State, 202 Path: backupPath, 203 } 204 } 205 } 206 207 // Return whatever state we have 208 return result, nil 209 } 210 211 func remoteState( 212 local *terraform.State, 213 localPath string, refresh bool) (*state.CacheState, error) { 214 // If there is no remote settings, it is an error 215 if local.Remote == nil { 216 return nil, fmt.Errorf("Remote state cache has no remote info") 217 } 218 219 // Initialize the remote client based on the local state 220 client, err := remote.NewClient(strings.ToLower(local.Remote.Type), local.Remote.Config) 221 if err != nil { 222 return nil, errwrap.Wrapf(fmt.Sprintf( 223 "Error initializing remote driver '%s': {{err}}", 224 local.Remote.Type), err) 225 } 226 227 // Create the remote client 228 durable := &remote.State{Client: client} 229 230 // Create the cached client 231 cache := &state.CacheState{ 232 Cache: &state.LocalState{Path: localPath}, 233 Durable: durable, 234 } 235 236 if refresh { 237 // Refresh the cache 238 if err := cache.RefreshState(); err != nil { 239 return nil, errwrap.Wrapf( 240 "Error reloading remote state: {{err}}", err) 241 } 242 switch cache.RefreshResult() { 243 // All the results below can be safely ignored since it means the 244 // pull was successful in some way. Noop = nothing happened. 245 // Init = both are empty. UpdateLocal = local state was older and 246 // updated. 247 // 248 // We don't have to do anything, the pull was successful. 249 case state.CacheRefreshNoop: 250 case state.CacheRefreshInit: 251 case state.CacheRefreshUpdateLocal: 252 253 // Our local state has a higher serial number than remote, so we 254 // want to explicitly sync the remote side with our local so that 255 // the remote gets the latest serial number. 256 case state.CacheRefreshLocalNewer: 257 // Write our local state out to the durable storage to start. 258 if err := cache.WriteState(local); err != nil { 259 return nil, errwrap.Wrapf( 260 "Error preparing remote state: {{err}}", err) 261 } 262 if err := cache.PersistState(); err != nil { 263 return nil, errwrap.Wrapf( 264 "Error preparing remote state: {{err}}", err) 265 } 266 default: 267 return nil, fmt.Errorf( 268 "Unknown refresh result: %s", cache.RefreshResult()) 269 } 270 } 271 272 return cache, nil 273 } 274 275 func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) { 276 // First create the local state for the path 277 local := &state.LocalState{Path: path} 278 if err := local.RefreshState(); err != nil { 279 return nil, err 280 } 281 localState := local.State() 282 283 return remoteState(localState, path, refresh) 284 }