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