diff --git a/command/state.go b/command/state.go deleted file mode 100644 index f1b5e2e0bc..0000000000 --- a/command/state.go +++ /dev/null @@ -1,284 +0,0 @@ -package command - -import ( - "fmt" - "os" - "strings" - - "github.com/hashicorp/errwrap" - "github.com/hashicorp/terraform/state" - "github.com/hashicorp/terraform/state/remote" - "github.com/hashicorp/terraform/terraform" -) - -// StateOpts are options to get the state for a command. -type StateOpts struct { - // LocalPath is the path where the state is stored locally. - // - // LocalPathOut is the path where the local state will be saved. If this - // isn't set, it will be saved back to LocalPath. - LocalPath string - LocalPathOut string - - // RemotePath is the path where the remote state cache would be. - // - // RemoteCache, if true, will set the result to only be the cache - // and not backed by any real durable storage. - RemotePath string - RemoteCacheOnly bool - RemoteRefresh bool - - // BackupPath is the path where the backup will be placed. If not set, - // it is assumed to be the path where the state is stored locally - // plus the DefaultBackupExtension. - BackupPath string - - // ForceState is a state structure to force the value to be. This - // is used by Terraform plans (which contain their state). - ForceState *terraform.State -} - -// StateResult is the result of calling State and holds various different -// State implementations so they can be accessed directly. -type StateResult struct { - // State is the final outer state that should be used for all - // _real_ reads/writes. - // - // StatePath is the local path where the state will be stored or - // cached, no matter whether State is local or remote. - State state.State - StatePath string - - // Local and Remote are the local/remote state implementations, raw - // and unwrapped by any backups. The paths here are the paths where - // these state files would be saved. - Local *state.LocalState - LocalPath string - Remote *state.CacheState - RemotePath string -} - -// State returns the proper state.State implementation to represent the -// current environment. -// -// localPath is the path to where state would be if stored locally. -// dataDir is the path to the local data directory where the remote state -// cache would be stored. -func State(opts *StateOpts) (*StateResult, error) { - result := new(StateResult) - - // Get the remote state cache path - if opts.RemotePath != "" { - result.RemotePath = opts.RemotePath - - var remote *state.CacheState - if opts.RemoteCacheOnly { - // Setup the in-memory state - ls := &state.LocalState{Path: opts.RemotePath} - if err := ls.RefreshState(); err != nil { - return nil, err - } - - // If we have a forced state, set it - if opts.ForceState != nil { - ls.SetState(opts.ForceState) - } - - is := &state.InmemState{} - is.WriteState(ls.State()) - - // Setupt he remote state, cache-only, and refresh it so that - // we have access to the state right away. - remote = &state.CacheState{ - Cache: ls, - Durable: is, - } - if err := remote.RefreshState(); err != nil { - return nil, err - } - } else { - // If we have a forced state that is remote, then we load that - if opts.ForceState != nil && - opts.ForceState.Remote != nil && - opts.ForceState.Remote.Type != "" { - var err error - remote, err = remoteState( - opts.ForceState, - opts.RemotePath, - false) - if err != nil { - return nil, err - } - } else { - // Only if we have no forced state, we check our normal - // remote path. - if _, err := os.Stat(opts.RemotePath); err == nil { - // We have a remote state, initialize that. - remote, err = remoteStateFromPath( - opts.RemotePath, - opts.RemoteRefresh) - if err != nil { - return nil, err - } - } - } - } - - if remote != nil { - result.State = remote - result.StatePath = opts.RemotePath - result.Remote = remote - } - } - - // If we have a forced state and we were able to initialize that - // into a remote state, we don't do any local state stuff. This is - // because normally we're able to test whether we should do local vs. - // remote by checking file existence. With ForceState, file existence - // doesn't work because neither may exist, so we use state attributes. - if opts.ForceState != nil && result.Remote != nil { - opts.LocalPath = "" - } - - // Do we have a local state? - if opts.LocalPath != "" { - local := &state.LocalState{ - Path: opts.LocalPath, - PathOut: opts.LocalPathOut, - } - - // Always store it in the result even if we're not using it - result.Local = local - result.LocalPath = local.Path - if local.PathOut != "" { - result.LocalPath = local.PathOut - } - - // If we're forcing, then set it - if opts.ForceState != nil { - local.SetState(opts.ForceState) - } else { - // If we're not forcing, then we load the state directly - // from disk. - err := local.RefreshState() - if err == nil { - if result.State != nil && !result.State.State().Empty() { - if !local.State().Empty() { - // We already have a remote state... that is an error. - return nil, fmt.Errorf( - "Remote state found, but state file '%s' also present.", - opts.LocalPath) - } - - // Empty state - local = nil - } - } - if err != nil { - return nil, errwrap.Wrapf( - "Error reading local state: {{err}}", err) - } - } - - if local != nil { - result.State = local - result.StatePath = opts.LocalPath - if opts.LocalPathOut != "" { - result.StatePath = opts.LocalPathOut - } - } - } - - // If we have a result, make sure to back it up - if result.State != nil { - backupPath := result.StatePath + DefaultBackupExtension - if opts.BackupPath != "" { - backupPath = opts.BackupPath - } - - if backupPath != "-" { - result.State = &state.BackupState{ - Real: result.State, - Path: backupPath, - } - } - } - - // Return whatever state we have - return result, nil -} - -func remoteState( - local *terraform.State, - localPath string, refresh bool) (*state.CacheState, error) { - // If there is no remote settings, it is an error - if local.Remote == nil { - return nil, fmt.Errorf("Remote state cache has no remote info") - } - - // Initialize the remote client based on the local state - client, err := remote.NewClient(strings.ToLower(local.Remote.Type), local.Remote.Config) - if err != nil { - return nil, errwrap.Wrapf(fmt.Sprintf( - "Error initializing remote driver '%s': {{err}}", - local.Remote.Type), err) - } - - // Create the remote client - durable := &remote.State{Client: client} - - // Create the cached client - cache := &state.CacheState{ - Cache: &state.LocalState{Path: localPath}, - Durable: durable, - } - - if refresh { - // Refresh the cache - if err := cache.RefreshState(); err != nil { - return nil, errwrap.Wrapf( - "Error reloading remote state: {{err}}", err) - } - switch cache.RefreshResult() { - // All the results below can be safely ignored since it means the - // pull was successful in some way. Noop = nothing happened. - // Init = both are empty. UpdateLocal = local state was older and - // updated. - // - // We don't have to do anything, the pull was successful. - case state.CacheRefreshNoop: - case state.CacheRefreshInit: - case state.CacheRefreshUpdateLocal: - - // Our local state has a higher serial number than remote, so we - // want to explicitly sync the remote side with our local so that - // the remote gets the latest serial number. - case state.CacheRefreshLocalNewer: - // Write our local state out to the durable storage to start. - if err := cache.WriteState(local); err != nil { - return nil, errwrap.Wrapf( - "Error preparing remote state: {{err}}", err) - } - if err := cache.PersistState(); err != nil { - return nil, errwrap.Wrapf( - "Error preparing remote state: {{err}}", err) - } - default: - return nil, fmt.Errorf( - "Unknown refresh result: %s", cache.RefreshResult()) - } - } - - return cache, nil -} - -func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) { - // First create the local state for the path - local := &state.LocalState{Path: path} - if err := local.RefreshState(); err != nil { - return nil, err - } - localState := local.State() - - return remoteState(localState, path, refresh) -}