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