github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/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 result.State = &state.BackupState{ 158 Real: result.State, 159 Path: backupPath, 160 } 161 } 162 163 // Return whatever state we have 164 return result, nil 165 } 166 167 // StateFromPlan gets our state from the plan. 168 func StateFromPlan( 169 localPath string, plan *terraform.Plan) (state.State, string, error) { 170 var result state.State 171 resultPath := localPath 172 if plan != nil && plan.State != nil && 173 plan.State.Remote != nil && plan.State.Remote.Type != "" { 174 var err error 175 176 // It looks like we have a remote state in the plan, so 177 // we have to initialize that. 178 resultPath = filepath.Join(DefaultDataDir, DefaultStateFilename) 179 result, err = remoteState(plan.State, resultPath, false) 180 if err != nil { 181 return nil, "", err 182 } 183 } 184 185 if result == nil { 186 local := &state.LocalState{Path: resultPath} 187 local.SetState(plan.State) 188 result = local 189 } 190 191 // If we have a result, make sure to back it up 192 result = &state.BackupState{ 193 Real: result, 194 Path: resultPath + DefaultBackupExtention, 195 } 196 197 return result, resultPath, nil 198 } 199 200 func remoteState( 201 local *terraform.State, 202 localPath string, refresh bool) (*state.CacheState, error) { 203 // If there is no remote settings, it is an error 204 if local.Remote == nil { 205 return nil, fmt.Errorf("Remote state cache has no remote info") 206 } 207 208 // Initialize the remote client based on the local state 209 client, err := remote.NewClient(local.Remote.Type, local.Remote.Config) 210 if err != nil { 211 return nil, errwrap.Wrapf(fmt.Sprintf( 212 "Error initializing remote driver '%s': {{err}}", 213 local.Remote.Type), err) 214 } 215 216 // Create the remote client 217 durable := &remote.State{Client: client} 218 219 // Create the cached client 220 cache := &state.CacheState{ 221 Cache: &state.LocalState{Path: localPath}, 222 Durable: durable, 223 } 224 225 if refresh { 226 // Refresh the cache 227 if err := cache.RefreshState(); err != nil { 228 return nil, errwrap.Wrapf( 229 "Error reloading remote state: {{err}}", err) 230 } 231 switch cache.RefreshResult() { 232 case state.CacheRefreshNoop: 233 case state.CacheRefreshInit: 234 case state.CacheRefreshLocalNewer: 235 case state.CacheRefreshUpdateLocal: 236 // Write our local state out to the durable storage to start. 237 if err := cache.WriteState(local); err != nil { 238 return nil, errwrap.Wrapf( 239 "Error preparing remote state: {{err}}", err) 240 } 241 if err := cache.PersistState(); err != nil { 242 return nil, errwrap.Wrapf( 243 "Error preparing remote state: {{err}}", err) 244 } 245 default: 246 return nil, errwrap.Wrapf( 247 "Error initilizing remote state: {{err}}", err) 248 } 249 } 250 251 return cache, nil 252 } 253 254 func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) { 255 // First create the local state for the path 256 local := &state.LocalState{Path: path} 257 if err := local.RefreshState(); err != nil { 258 return nil, err 259 } 260 localState := local.State() 261 262 return remoteState(localState, path, refresh) 263 }