command: deal with plan states
This commit is contained in:
parent
89d3a10adf
commit
4ec63bc2ef
|
@ -5,7 +5,6 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -41,11 +40,6 @@ type Meta struct {
|
||||||
color bool
|
color bool
|
||||||
oldUi cli.Ui
|
oldUi cli.Ui
|
||||||
|
|
||||||
// useRemoteState is enabled if we are using remote state storage
|
|
||||||
// This is set when the context is loaded if we read from a remote
|
|
||||||
// enabled state file.
|
|
||||||
useRemoteState bool
|
|
||||||
|
|
||||||
// statePath is the path to the state file. If this is empty, then
|
// statePath is the path to the state file. If this is empty, then
|
||||||
// no state will be loaded. It is also okay for this to be a path to
|
// no state will be loaded. It is also okay for this to be a path to
|
||||||
// a file that doesn't exist; it is assumed that this means that there
|
// a file that doesn't exist; it is assumed that this means that there
|
||||||
|
@ -101,14 +95,16 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) {
|
||||||
plan, err := terraform.ReadPlan(f)
|
plan, err := terraform.ReadPlan(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if remote state is enabled, but do not refresh.
|
// Setup our state
|
||||||
// Since a plan is supposed to lock-in the changes, we do not
|
state, statePath, err := StateFromPlan(m.statePath, plan)
|
||||||
// attempt a state refresh.
|
if err != nil {
|
||||||
if plan != nil && plan.State != nil && plan.State.Remote != nil && plan.State.Remote.Type != "" {
|
return nil, false, fmt.Errorf("Error loading plan: %s", err)
|
||||||
log.Printf("[INFO] Enabling remote state from plan")
|
|
||||||
m.useRemoteState = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set our state
|
||||||
|
m.state = state
|
||||||
|
m.stateOutPath = statePath
|
||||||
|
|
||||||
if len(m.variables) > 0 {
|
if len(m.variables) > 0 {
|
||||||
return nil, false, fmt.Errorf(
|
return nil, false, fmt.Errorf(
|
||||||
"You can't set variables with the '-var' or '-var-file' flag\n" +
|
"You can't set variables with the '-var' or '-var-file' flag\n" +
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// State returns the proper state.State implementation to represent the
|
// State returns the proper state.State implementation to represent the
|
||||||
|
@ -24,7 +25,7 @@ func State(localPath string) (state.State, string, error) {
|
||||||
remoteCachePath := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
remoteCachePath := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||||
if _, err := os.Stat(remoteCachePath); err == nil {
|
if _, err := os.Stat(remoteCachePath); err == nil {
|
||||||
// We have a remote state, initialize that.
|
// We have a remote state, initialize that.
|
||||||
result, err = remoteState(remoteCachePath)
|
result, err = remoteStateFromPath(remoteCachePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -62,29 +63,65 @@ func State(localPath string) (state.State, string, error) {
|
||||||
resultPath = localPath
|
resultPath = localPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a result, make sure to back it up
|
||||||
|
if result != nil {
|
||||||
|
result = &state.BackupState{
|
||||||
|
Real: result,
|
||||||
|
Path: resultPath + DefaultBackupExtention,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return whatever state we have
|
// Return whatever state we have
|
||||||
return result, resultPath, nil
|
return result, resultPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func remoteState(path string) (state.State, error) {
|
// StateFromPlan gets our state from the plan.
|
||||||
// First create the local state for the path
|
func StateFromPlan(
|
||||||
local := &state.LocalState{Path: path}
|
localPath string, plan *terraform.Plan) (state.State, string, error) {
|
||||||
if err := local.RefreshState(); err != nil {
|
var result state.State
|
||||||
return nil, err
|
resultPath := localPath
|
||||||
}
|
if plan != nil && plan.State != nil &&
|
||||||
localState := local.State()
|
plan.State.Remote != nil && plan.State.Remote.Type != "" {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// It looks like we have a remote state in the plan, so
|
||||||
|
// we have to initialize that.
|
||||||
|
resultPath = filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||||
|
result, err = remoteState(plan.State, resultPath, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
local := &state.LocalState{Path: resultPath}
|
||||||
|
local.SetState(plan.State)
|
||||||
|
result = local
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a result, make sure to back it up
|
||||||
|
result = &state.BackupState{
|
||||||
|
Real: result,
|
||||||
|
Path: resultPath + DefaultBackupExtention,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, resultPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func remoteState(
|
||||||
|
local *terraform.State,
|
||||||
|
localPath string, refresh bool) (state.State, error) {
|
||||||
// If there is no remote settings, it is an error
|
// If there is no remote settings, it is an error
|
||||||
if localState.Remote == nil {
|
if local.Remote == nil {
|
||||||
return nil, fmt.Errorf("Remote state cache has no remote info")
|
return nil, fmt.Errorf("Remote state cache has no remote info")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the remote client based on the local state
|
// Initialize the remote client based on the local state
|
||||||
client, err := remote.NewClient(localState.Remote.Type, localState.Remote.Config)
|
client, err := remote.NewClient(local.Remote.Type, local.Remote.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrapf(fmt.Sprintf(
|
return nil, errwrap.Wrapf(fmt.Sprintf(
|
||||||
"Error initializing remote driver '%s': {{err}}",
|
"Error initializing remote driver '%s': {{err}}",
|
||||||
localState.Remote.Type), err)
|
local.Remote.Type), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the remote client
|
// Create the remote client
|
||||||
|
@ -92,10 +129,11 @@ func remoteState(path string) (state.State, error) {
|
||||||
|
|
||||||
// Create the cached client
|
// Create the cached client
|
||||||
cache := &state.CacheState{
|
cache := &state.CacheState{
|
||||||
Cache: local,
|
Cache: &state.LocalState{Path: localPath},
|
||||||
Durable: durable,
|
Durable: durable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if refresh {
|
||||||
// Refresh the cache
|
// Refresh the cache
|
||||||
if err := cache.RefreshState(); err != nil {
|
if err := cache.RefreshState(); err != nil {
|
||||||
return nil, errwrap.Wrapf(
|
return nil, errwrap.Wrapf(
|
||||||
|
@ -107,15 +145,30 @@ func remoteState(path string) (state.State, error) {
|
||||||
case state.CacheRefreshLocalNewer:
|
case state.CacheRefreshLocalNewer:
|
||||||
case state.CacheRefreshUpdateLocal:
|
case state.CacheRefreshUpdateLocal:
|
||||||
// Write our local state out to the durable storage to start.
|
// Write our local state out to the durable storage to start.
|
||||||
if err := cache.WriteState(localState); err != nil {
|
if err := cache.WriteState(local); err != nil {
|
||||||
return nil, errwrap.Wrapf("Error preparing remote state: {{err}}", err)
|
return nil, errwrap.Wrapf(
|
||||||
|
"Error preparing remote state: {{err}}", err)
|
||||||
}
|
}
|
||||||
if err := cache.PersistState(); err != nil {
|
if err := cache.PersistState(); err != nil {
|
||||||
return nil, errwrap.Wrapf("Error preparing remote state: {{err}}", err)
|
return nil, errwrap.Wrapf(
|
||||||
|
"Error preparing remote state: {{err}}", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errwrap.Wrapf("Error initilizing remote state: {{err}}", err)
|
return nil, errwrap.Wrapf(
|
||||||
|
"Error initilizing remote state: {{err}}", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache, nil
|
return cache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remoteStateFromPath(path string) (state.State, 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, true)
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,11 @@ type LocalState struct {
|
||||||
written bool
|
written bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetState will force a specific state in-memory for this local state.
|
||||||
|
func (s *LocalState) SetState(state *terraform.State) {
|
||||||
|
s.state = state
|
||||||
|
}
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *LocalState) State() *terraform.State {
|
func (s *LocalState) State() *terraform.State {
|
||||||
return s.state
|
return s.state
|
||||||
|
@ -34,6 +39,16 @@ func (s *LocalState) WriteState(state *terraform.State) error {
|
||||||
path = s.Path
|
path = s.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we don't have any state, we actually delete the file if it exists
|
||||||
|
if state == nil {
|
||||||
|
err := os.Remove(path)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue