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  }