github.com/jdextraze/terraform@v0.6.17-0.20160511153921-e33847c8a8af/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 + DefaultBackupExtension
   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 + DefaultBackupExtension,
   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  }