github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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  		case state.CacheRefreshNoop:
   235  		case state.CacheRefreshInit:
   236  		case state.CacheRefreshLocalNewer:
   237  		case state.CacheRefreshUpdateLocal:
   238  			// Write our local state out to the durable storage to start.
   239  			if err := cache.WriteState(local); err != nil {
   240  				return nil, errwrap.Wrapf(
   241  					"Error preparing remote state: {{err}}", err)
   242  			}
   243  			if err := cache.PersistState(); err != nil {
   244  				return nil, errwrap.Wrapf(
   245  					"Error preparing remote state: {{err}}", err)
   246  			}
   247  		default:
   248  			return nil, errwrap.Wrapf(
   249  				"Error initilizing remote state: {{err}}", err)
   250  		}
   251  	}
   252  
   253  	return cache, nil
   254  }
   255  
   256  func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) {
   257  	// First create the local state for the path
   258  	local := &state.LocalState{Path: path}
   259  	if err := local.RefreshState(); err != nil {
   260  		return nil, err
   261  	}
   262  	localState := local.State()
   263  
   264  	return remoteState(localState, path, refresh)
   265  }