github.com/rhenning/terraform@v0.8.0-beta2/command/state.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     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  	// ForceState is a state structure to force the value to be. This
    37  	// is used by Terraform plans (which contain their state).
    38  	ForceState *terraform.State
    39  }
    40  
    41  // StateResult is the result of calling State and holds various different
    42  // State implementations so they can be accessed directly.
    43  type StateResult struct {
    44  	// State is the final outer state that should be used for all
    45  	// _real_ reads/writes.
    46  	//
    47  	// StatePath is the local path where the state will be stored or
    48  	// cached, no matter whether State is local or remote.
    49  	State     state.State
    50  	StatePath string
    51  
    52  	// Local and Remote are the local/remote state implementations, raw
    53  	// and unwrapped by any backups. The paths here are the paths where
    54  	// these state files would be saved.
    55  	Local      *state.LocalState
    56  	LocalPath  string
    57  	Remote     *state.CacheState
    58  	RemotePath string
    59  }
    60  
    61  // State returns the proper state.State implementation to represent the
    62  // current environment.
    63  //
    64  // localPath is the path to where state would be if stored locally.
    65  // dataDir is the path to the local data directory where the remote state
    66  // cache would be stored.
    67  func State(opts *StateOpts) (*StateResult, error) {
    68  	result := new(StateResult)
    69  
    70  	// Get the remote state cache path
    71  	if opts.RemotePath != "" {
    72  		result.RemotePath = opts.RemotePath
    73  
    74  		var remote *state.CacheState
    75  		if opts.RemoteCacheOnly {
    76  			// Setup the in-memory state
    77  			ls := &state.LocalState{Path: opts.RemotePath}
    78  			if err := ls.RefreshState(); err != nil {
    79  				return nil, err
    80  			}
    81  
    82  			// If we have a forced state, set it
    83  			if opts.ForceState != nil {
    84  				ls.SetState(opts.ForceState)
    85  			}
    86  
    87  			is := &state.InmemState{}
    88  			is.WriteState(ls.State())
    89  
    90  			// Setupt he remote state, cache-only, and refresh it so that
    91  			// we have access to the state right away.
    92  			remote = &state.CacheState{
    93  				Cache:   ls,
    94  				Durable: is,
    95  			}
    96  			if err := remote.RefreshState(); err != nil {
    97  				return nil, err
    98  			}
    99  		} else {
   100  			// If we have a forced state that is remote, then we load that
   101  			if opts.ForceState != nil &&
   102  				opts.ForceState.Remote != nil &&
   103  				opts.ForceState.Remote.Type != "" {
   104  				var err error
   105  				remote, err = remoteState(
   106  					opts.ForceState,
   107  					opts.RemotePath,
   108  					false)
   109  				if err != nil {
   110  					return nil, err
   111  				}
   112  			} else {
   113  				// Only if we have no forced state, we check our normal
   114  				// remote path.
   115  				if _, err := os.Stat(opts.RemotePath); err == nil {
   116  					// We have a remote state, initialize that.
   117  					remote, err = remoteStateFromPath(
   118  						opts.RemotePath,
   119  						opts.RemoteRefresh)
   120  					if err != nil {
   121  						return nil, err
   122  					}
   123  				}
   124  			}
   125  		}
   126  
   127  		if remote != nil {
   128  			result.State = remote
   129  			result.StatePath = opts.RemotePath
   130  			result.Remote = remote
   131  		}
   132  	}
   133  
   134  	// If we have a forced state and we were able to initialize that
   135  	// into a remote state, we don't do any local state stuff. This is
   136  	// because normally we're able to test whether we should do local vs.
   137  	// remote by checking file existence. With ForceState, file existence
   138  	// doesn't work because neither may exist, so we use state attributes.
   139  	if opts.ForceState != nil && result.Remote != nil {
   140  		opts.LocalPath = ""
   141  	}
   142  
   143  	// Do we have a local state?
   144  	if opts.LocalPath != "" {
   145  		local := &state.LocalState{
   146  			Path:    opts.LocalPath,
   147  			PathOut: opts.LocalPathOut,
   148  		}
   149  
   150  		// Always store it in the result even if we're not using it
   151  		result.Local = local
   152  		result.LocalPath = local.Path
   153  		if local.PathOut != "" {
   154  			result.LocalPath = local.PathOut
   155  		}
   156  
   157  		// If we're forcing, then set it
   158  		if opts.ForceState != nil {
   159  			local.SetState(opts.ForceState)
   160  		} else {
   161  			// If we're not forcing, then we load the state directly
   162  			// from disk.
   163  			err := local.RefreshState()
   164  			if err == nil {
   165  				if result.State != nil && !result.State.State().Empty() {
   166  					if !local.State().Empty() {
   167  						// We already have a remote state... that is an error.
   168  						return nil, fmt.Errorf(
   169  							"Remote state found, but state file '%s' also present.",
   170  							opts.LocalPath)
   171  					}
   172  
   173  					// Empty state
   174  					local = nil
   175  				}
   176  			}
   177  			if err != nil {
   178  				return nil, errwrap.Wrapf(
   179  					"Error reading local state: {{err}}", err)
   180  			}
   181  		}
   182  
   183  		if local != nil {
   184  			result.State = local
   185  			result.StatePath = opts.LocalPath
   186  			if opts.LocalPathOut != "" {
   187  				result.StatePath = opts.LocalPathOut
   188  			}
   189  		}
   190  	}
   191  
   192  	// If we have a result, make sure to back it up
   193  	if result.State != nil {
   194  		backupPath := result.StatePath + DefaultBackupExtension
   195  		if opts.BackupPath != "" {
   196  			backupPath = opts.BackupPath
   197  		}
   198  
   199  		if backupPath != "-" {
   200  			result.State = &state.BackupState{
   201  				Real: result.State,
   202  				Path: backupPath,
   203  			}
   204  		}
   205  	}
   206  
   207  	// Return whatever state we have
   208  	return result, nil
   209  }
   210  
   211  func remoteState(
   212  	local *terraform.State,
   213  	localPath string, refresh bool) (*state.CacheState, error) {
   214  	// If there is no remote settings, it is an error
   215  	if local.Remote == nil {
   216  		return nil, fmt.Errorf("Remote state cache has no remote info")
   217  	}
   218  
   219  	// Initialize the remote client based on the local state
   220  	client, err := remote.NewClient(strings.ToLower(local.Remote.Type), local.Remote.Config)
   221  	if err != nil {
   222  		return nil, errwrap.Wrapf(fmt.Sprintf(
   223  			"Error initializing remote driver '%s': {{err}}",
   224  			local.Remote.Type), err)
   225  	}
   226  
   227  	// Create the remote client
   228  	durable := &remote.State{Client: client}
   229  
   230  	// Create the cached client
   231  	cache := &state.CacheState{
   232  		Cache:   &state.LocalState{Path: localPath},
   233  		Durable: durable,
   234  	}
   235  
   236  	if refresh {
   237  		// Refresh the cache
   238  		if err := cache.RefreshState(); err != nil {
   239  			return nil, errwrap.Wrapf(
   240  				"Error reloading remote state: {{err}}", err)
   241  		}
   242  		switch cache.RefreshResult() {
   243  		// All the results below can be safely ignored since it means the
   244  		// pull was successful in some way. Noop = nothing happened.
   245  		// Init = both are empty. UpdateLocal = local state was older and
   246  		// updated.
   247  		//
   248  		// We don't have to do anything, the pull was successful.
   249  		case state.CacheRefreshNoop:
   250  		case state.CacheRefreshInit:
   251  		case state.CacheRefreshUpdateLocal:
   252  
   253  		// Our local state has a higher serial number than remote, so we
   254  		// want to explicitly sync the remote side with our local so that
   255  		// the remote gets the latest serial number.
   256  		case state.CacheRefreshLocalNewer:
   257  			// Write our local state out to the durable storage to start.
   258  			if err := cache.WriteState(local); err != nil {
   259  				return nil, errwrap.Wrapf(
   260  					"Error preparing remote state: {{err}}", err)
   261  			}
   262  			if err := cache.PersistState(); err != nil {
   263  				return nil, errwrap.Wrapf(
   264  					"Error preparing remote state: {{err}}", err)
   265  			}
   266  		default:
   267  			return nil, fmt.Errorf(
   268  				"Unknown refresh result: %s", cache.RefreshResult())
   269  		}
   270  	}
   271  
   272  	return cache, nil
   273  }
   274  
   275  func remoteStateFromPath(path string, refresh bool) (*state.CacheState, error) {
   276  	// First create the local state for the path
   277  	local := &state.LocalState{Path: path}
   278  	if err := local.RefreshState(); err != nil {
   279  		return nil, err
   280  	}
   281  	localState := local.State()
   282  
   283  	return remoteState(localState, path, refresh)
   284  }