terraform: Ugly huge change to weave in new State and Plan types

Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.

The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.

The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.

Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
This commit is contained in:
Martin Atkins 2018-08-14 14:24:45 -07:00
parent cf6892275a
commit a3403f2766
206 changed files with 7768 additions and 10450 deletions

View File

@ -28,7 +28,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -80,7 +79,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -152,7 +150,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -178,7 +175,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9}, End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -250,7 +246,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10}, End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -269,7 +264,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14}, End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -313,7 +307,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -333,7 +326,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21}, End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -387,7 +379,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -433,7 +424,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4}, End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -469,7 +459,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19}, End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -517,7 +506,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7}, End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -569,7 +557,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },
@ -641,7 +628,6 @@ func TestParseRef(t *testing.T) {
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
}, },
Remaining: hcl.Traversal{},
}, },
``, ``,
}, },

View File

@ -50,6 +50,8 @@ type Backend struct {
opLock sync.Mutex opLock sync.Mutex
} }
var _ backend.Backend = (*Backend)(nil)
func (b *Backend) ConfigSchema() *configschema.Block { func (b *Backend) ConfigSchema() *configschema.Block {
return &configschema.Block{ return &configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
@ -160,15 +162,15 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
return diags return diags
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }

View File

@ -13,10 +13,13 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -25,25 +28,18 @@ import (
// backend must have. This state cannot be deleted. // backend must have. This state cannot be deleted.
const DefaultStateName = "default" const DefaultStateName = "default"
// This must be returned rather than a custom error so that the Terraform // ErrWorkspacesNotSupported is an error returned when a caller attempts
// CLI can detect it and handle it appropriately. // to perform an operation on a workspace other than "default" for a
var ( // backend that doesn't support multiple workspaces.
// ErrDefaultStateNotSupported is returned when an operation does not support //
// using the default state, but requires a named state to be selected. // The caller can detect this to do special fallback behavior or produce
ErrDefaultStateNotSupported = errors.New("default state not supported\n" + // a specific, helpful error message.
"You can create a new workspace with the \"workspace new\" command.") var ErrWorkspacesNotSupported = errors.New("workspaces not supported")
// ErrNamedStatesNotSupported is returned when a named state operation // ErrNamedStatesNotSupported is an older name for ErrWorkspacesNotSupported.
// isn't supported. //
ErrNamedStatesNotSupported = errors.New("named states not supported") // Deprecated: Use ErrWorkspacesNotSupported instead.
var ErrNamedStatesNotSupported = ErrWorkspacesNotSupported
// ErrOperationNotSupported is returned when an unsupported operation
// is detected by the configured backend.
ErrOperationNotSupported = errors.New("operation not supported")
)
// InitFn is used to initialize a new backend.
type InitFn func() Backend
// Backend is the minimal interface that must be implemented to enable Terraform. // Backend is the minimal interface that must be implemented to enable Terraform.
type Backend interface { type Backend interface {
@ -87,25 +83,26 @@ type Backend interface {
// is undefined and no other methods may be called. // is undefined and no other methods may be called.
Configure(cty.Value) tfdiags.Diagnostics Configure(cty.Value) tfdiags.Diagnostics
// State returns the current state for this environment. This state may // StateMgr returns the state manager for the given workspace name.
// not be loaded locally: the proper APIs should be called on state.State
// to load the state. If the state.State is a state.Locker, it's up to the
// caller to call Lock and Unlock as needed.
// //
// If the named state doesn't exist it will be created. The "default" state // If the returned state manager also implements statemgr.Locker then
// is always assumed to exist. // it's the caller's responsibility to call Lock and Unlock as appropriate.
State(name string) (state.State, error)
// DeleteState removes the named state if it exists. It is an error
// to delete the default state.
// //
// DeleteState does not prevent deleting a state that is in use. It is the // If the named workspace doesn't exist, or if it has no state, it will
// responsibility of the caller to hold a Lock on the state when calling // be created either immediately on this call or the first time
// this method. // PersistState is called, depending on the state manager implementation.
DeleteState(name string) error StateMgr(workspace string) (statemgr.Full, error)
// States returns a list of configured named states. // DeleteWorkspace removes the workspace with the given name if it exists.
States() ([]string, error) //
// DeleteWorkspace cannot prevent deleting a state that is in use. It is
// the responsibility of the caller to hold a Lock for the state manager
// belonging to this workspace before calling this method.
DeleteWorkspace(name string) error
// States returns a list of the names of all of the workspaces that exist
// in this backend.
Workspaces() ([]string, error)
} }
// Enhanced implements additional behavior on top of a normal backend. // Enhanced implements additional behavior on top of a normal backend.
@ -136,7 +133,7 @@ type Enhanced interface {
type Local interface { type Local interface {
// Context returns a runnable terraform Context. The operation parameter // Context returns a runnable terraform Context. The operation parameter
// doesn't need a Type set but it needs other options set such as Module. // doesn't need a Type set but it needs other options set such as Module.
Context(*Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) Context(*Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics)
} }
// An operation represents an operation for Terraform to execute. // An operation represents an operation for Terraform to execute.
@ -166,7 +163,7 @@ type Operation struct {
PlanId string PlanId string
PlanRefresh bool // PlanRefresh will do a refresh before a plan PlanRefresh bool // PlanRefresh will do a refresh before a plan
PlanOutPath string // PlanOutPath is the path to save the plan PlanOutPath string // PlanOutPath is the path to save the plan
PlanOutBackend *terraform.BackendState PlanOutBackend *plans.Backend
// ConfigDir is the path to the directory containing the configuration's // ConfigDir is the path to the directory containing the configuration's
// root module. // root module.
@ -178,11 +175,10 @@ type Operation struct {
// Plan is a plan that was passed as an argument. This is valid for // Plan is a plan that was passed as an argument. This is valid for
// plan and apply arguments but may not work for all backends. // plan and apply arguments but may not work for all backends.
Plan *terraform.Plan PlanFile *planfile.Reader
// The options below are more self-explanatory and affect the runtime // The options below are more self-explanatory and affect the runtime
// behavior of the operation. // behavior of the operation.
AutoApprove bool
Destroy bool Destroy bool
Targets []addrs.Targetable Targets []addrs.Targetable
Variables map[string]UnparsedVariableValue Variables map[string]UnparsedVariableValue
@ -259,7 +255,7 @@ type RunningOperation struct {
// State is the final state after the operation completed. Persisting // State is the final state after the operation completed. Persisting
// this state is managed by the backend. This should only be read // this state is managed by the backend. This should only be read
// after the operation completes to avoid read/write races. // after the operation completes to avoid read/write races.
State *terraform.State State *states.State
} }
// OperationResult describes the result status of an operation. // OperationResult describes the result status of an operation.

View File

@ -18,7 +18,7 @@ import (
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
@ -65,7 +65,7 @@ type Local struct {
// We only want to create a single instance of a local state, so store them // We only want to create a single instance of a local state, so store them
// here as they're loaded. // here as they're loaded.
states map[string]state.State states map[string]statemgr.Full
// Terraform context. Many of these will be overridden or merged by // Terraform context. Many of these will be overridden or merged by
// Operation. See Operation for more details. // Operation. See Operation for more details.
@ -97,8 +97,11 @@ type Local struct {
RunningInAutomation bool RunningInAutomation bool
opLock sync.Mutex opLock sync.Mutex
once sync.Once
} }
var _ backend.Backend = (*Local)(nil)
func (b *Local) ConfigSchema() *configschema.Block { func (b *Local) ConfigSchema() *configschema.Block {
if b.Backend != nil { if b.Backend != nil {
return b.Backend.ConfigSchema() return b.Backend.ConfigSchema()
@ -184,67 +187,10 @@ func (b *Local) Configure(obj cty.Value) tfdiags.Diagnostics {
return diags return diags
} }
func (b *Local) State(name string) (state.State, error) { func (b *Local) Workspaces() ([]string, error) {
statePath, stateOutPath, backupPath := b.StatePaths(name)
// If we have a backend handling state, delegate to that.
if b.Backend != nil {
return b.Backend.State(name)
}
if s, ok := b.states[name]; ok {
return s, nil
}
if err := b.createState(name); err != nil {
return nil, err
}
// Otherwise, we need to load the state.
var s state.State = &state.LocalState{
Path: statePath,
PathOut: stateOutPath,
}
// If we are backing up the state, wrap it
if backupPath != "" {
s = &state.BackupState{
Real: s,
Path: backupPath,
}
}
if b.states == nil {
b.states = map[string]state.State{}
}
b.states[name] = s
return s, nil
}
// DeleteState removes a named state.
// The "default" state cannot be removed.
func (b *Local) DeleteState(name string) error {
// If we have a backend handling state, defer to that. // If we have a backend handling state, defer to that.
if b.Backend != nil { if b.Backend != nil {
return b.Backend.DeleteState(name) return b.Backend.Workspaces()
}
if name == "" {
return errors.New("empty state name")
}
if name == backend.DefaultStateName {
return errors.New("cannot delete default state")
}
delete(b.states, name)
return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
}
func (b *Local) States() ([]string, error) {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.States()
} }
// the listing always start with "default" // the listing always start with "default"
@ -272,6 +218,55 @@ func (b *Local) States() ([]string, error) {
return envs, nil return envs, nil
} }
// DeleteWorkspace removes a workspace.
//
// The "default" workspace cannot be removed.
func (b *Local) DeleteWorkspace(name string) error {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.DeleteWorkspace(name)
}
if name == "" {
return errors.New("empty state name")
}
if name == backend.DefaultStateName {
return errors.New("cannot delete default state")
}
delete(b.states, name)
return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
}
func (b *Local) StateMgr(name string) (statemgr.Full, error) {
statePath, stateOutPath, backupPath := b.StatePaths(name)
// If we have a backend handling state, delegate to that.
if b.Backend != nil {
return b.Backend.StateMgr(name)
}
if s, ok := b.states[name]; ok {
return s, nil
}
if err := b.createState(name); err != nil {
return nil, err
}
s := statemgr.NewFilesystemBetweenPaths(statePath, stateOutPath)
if backupPath != "" {
s.SetBackupPath(backupPath)
}
if b.states == nil {
b.states = map[string]statemgr.Full{}
}
b.states[name] = s
return s, nil
}
// Operation implements backend.Enhanced // Operation implements backend.Enhanced
// //
// This will initialize an in-memory terraform.Context to perform the // This will initialize an in-memory terraform.Context to perform the
@ -347,14 +342,14 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
return runningOp, nil return runningOp, nil
} }
// opWait waits for the operation to complete, and a stop signal or a // opWait wats for the operation to complete, and a stop signal or a
// cancelation signal. // cancelation signal.
func (b *Local) opWait( func (b *Local) opWait(
doneCh <-chan struct{}, doneCh <-chan struct{},
stopCtx context.Context, stopCtx context.Context,
cancelCtx context.Context, cancelCtx context.Context,
tfCtx *terraform.Context, tfCtx *terraform.Context,
opState state.State) (canceled bool) { opStateMgr statemgr.Persister) (canceled bool) {
// Wait for the operation to finish or for us to be interrupted so // Wait for the operation to finish or for us to be interrupted so
// we can handle it properly. // we can handle it properly.
select { select {
@ -365,7 +360,7 @@ func (b *Local) opWait(
// try to force a PersistState just in case the process is terminated // try to force a PersistState just in case the process is terminated
// before we can complete. // before we can complete.
if err := opState.PersistState(); err != nil { if err := opStateMgr.PersistState(); err != nil {
// We can't error out from here, but warn the user if there was an error. // We can't error out from here, but warn the user if there was an error.
// If this isn't transient, we will catch it again below, and // If this isn't transient, we will catch it again below, and
// attempt to save the state another way. // attempt to save the state another way.
@ -465,7 +460,7 @@ func (b *Local) schemaConfigure(ctx context.Context) error {
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
// configured from the CLI. // configured from the CLI.
func (b *Local) StatePaths(name string) (string, string, string) { func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) {
statePath := b.StatePath statePath := b.StatePath
stateOutPath := b.StateOutPath stateOutPath := b.StateOutPath
backupPath := b.StateBackupPath backupPath := b.StateBackupPath

View File

@ -8,9 +8,12 @@ import (
"log" "log"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -23,10 +26,11 @@ func (b *Local) opApply(
log.Printf("[INFO] backend/local: starting Apply operation") log.Printf("[INFO] backend/local: starting Apply operation")
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
var err error
// If we have a nil module at this point, then set it to an empty tree // If we have a nil module at this point, then set it to an empty tree
// to avoid any potential crashes. // to avoid any potential crashes.
if op.Plan == nil && !op.Destroy && !op.HasConfig() { if op.PlanFile == nil && !op.Destroy && !op.HasConfig() {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
"No configuration files", "No configuration files",
@ -47,9 +51,9 @@ func (b *Local) opApply(
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook) b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
// Get our context // Get our context
tfCtx, opState, err := b.context(op) tfCtx, _, opState, contextDiags := b.context(op)
if err != nil { diags = diags.Append(contextDiags)
diags = diags.Append(err) if contextDiags.HasErrors() {
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
return return
} }
@ -58,7 +62,7 @@ func (b *Local) opApply(
runningOp.State = tfCtx.State() runningOp.State = tfCtx.State()
// If we weren't given a plan, then we refresh/plan // If we weren't given a plan, then we refresh/plan
if op.Plan == nil { if op.PlanFile == nil {
// If we're refreshing before apply, perform that // If we're refreshing before apply, perform that
if op.PlanRefresh { if op.PlanRefresh {
log.Printf("[INFO] backend/local: apply calling Refresh") log.Printf("[INFO] backend/local: apply calling Refresh")
@ -80,7 +84,7 @@ func (b *Local) opApply(
return return
} }
dispPlan := format.NewPlan(plan) dispPlan := format.NewPlan(plan.Changes)
trivialPlan := dispPlan.Empty() trivialPlan := dispPlan.Empty()
hasUI := op.UIOut != nil && op.UIIn != nil hasUI := op.UIOut != nil && op.UIIn != nil
mustConfirm := hasUI && ((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove && !trivialPlan)) mustConfirm := hasUI && ((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove && !trivialPlan))
@ -133,10 +137,10 @@ func (b *Local) opApply(
} }
// Setup our hook for continuous state updates // Setup our hook for continuous state updates
stateHook.State = opState stateHook.StateMgr = opState
// Start the apply in a goroutine so that we can be interrupted. // Start the apply in a goroutine so that we can be interrupted.
var applyState *terraform.State var applyState *states.State
var applyDiags tfdiags.Diagnostics var applyDiags tfdiags.Diagnostics
doneCh := make(chan struct{}) doneCh := make(chan struct{})
go func() { go func() {
@ -152,14 +156,8 @@ func (b *Local) opApply(
// Store the final state // Store the final state
runningOp.State = applyState runningOp.State = applyState
err = statemgr.WriteAndPersist(opState, applyState)
// Persist the state if err != nil {
if err := opState.WriteState(applyState); err != nil {
diags = diags.Append(b.backupStateForError(applyState, err))
b.ReportResult(runningOp, diags)
return
}
if err := opState.PersistState(); err != nil {
diags = diags.Append(b.backupStateForError(applyState, err)) diags = diags.Append(b.backupStateForError(applyState, err))
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
return return
@ -211,10 +209,10 @@ func (b *Local) opApply(
// to local disk to help the user recover. This is a "last ditch effort" sort // to local disk to help the user recover. This is a "last ditch effort" sort
// of thing, so we really don't want to end up in this codepath; we should do // of thing, so we really don't want to end up in this codepath; we should do
// everything we possibly can to get the state saved _somewhere_. // everything we possibly can to get the state saved _somewhere_.
func (b *Local) backupStateForError(applyState *terraform.State, err error) error { func (b *Local) backupStateForError(applyState *states.State, err error) error {
b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err)) b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err))
local := &state.LocalState{Path: "errored.tfstate"} local := statemgr.NewFilesystem("errored.tfstate")
writeErr := local.WriteState(applyState) writeErr := local.WriteState(applyState)
if writeErr != nil { if writeErr != nil {
b.CLI.Error(fmt.Sprintf( b.CLI.Error(fmt.Sprintf(
@ -226,7 +224,10 @@ func (b *Local) backupStateForError(applyState *terraform.State, err error) erro
// but at least the user has _some_ path to recover if we end up // but at least the user has _some_ path to recover if we end up
// here for some reason. // here for some reason.
stateBuf := new(bytes.Buffer) stateBuf := new(bytes.Buffer)
jsonErr := terraform.WriteState(applyState, stateBuf) stateFile := &statefile.File{
State: applyState,
}
jsonErr := statefile.Write(stateFile, stateBuf)
if jsonErr != nil { if jsonErr != nil {
b.CLI.Error(fmt.Sprintf( b.CLI.Error(fmt.Sprintf(
"Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr, "Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr,

View File

@ -10,13 +10,15 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
) )
func TestLocal_applyBasic(t *testing.T) { func TestLocal_applyBasic(t *testing.T) {
@ -226,19 +228,17 @@ type backendWithFailingState struct {
Local Local
} }
func (b *backendWithFailingState) State(name string) (state.State, error) { func (b *backendWithFailingState) StateMgr(name string) (statemgr.Full, error) {
return &failingState{ return &failingState{
&state.LocalState{ statemgr.NewFilesystem("failing-state.tfstate"),
Path: "failing-state.tfstate",
},
}, nil }, nil
} }
type failingState struct { type failingState struct {
*state.LocalState *statemgr.Filesystem
} }
func (s failingState) WriteState(state *terraform.State) error { func (s failingState) WriteState(state *states.State) error {
return errors.New("fake failure") return errors.New("fake failure")
} }

View File

@ -2,18 +2,21 @@ package local
import ( import (
"context" "context"
"fmt"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
// backend.Local implementation. // backend.Local implementation.
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) { func (b *Local) Context(op *backend.Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics) {
// Make sure the type is invalid. We use this as a way to know not // Make sure the type is invalid. We use this as a way to know not
// to ask for input/validate. // to ask for input/validate.
op.Type = backend.OperationTypeInvalid op.Type = backend.OperationTypeInvalid
@ -24,27 +27,26 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State,
op.StateLocker = clistate.NewNoopLocker() op.StateLocker = clistate.NewNoopLocker()
} }
return b.context(op) ctx, _, stateMgr, diags := b.context(op)
return ctx, stateMgr, diags
} }
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) { func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.Snapshot, statemgr.Full, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
// Get the state. // Get the latest state.
s, err := b.State(op.Workspace) s, err := b.StateMgr(op.Workspace)
if err != nil { if err != nil {
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err)) diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
return nil, nil, diags return nil, nil, nil, diags
} }
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil { if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err)) diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
return nil, nil, diags return nil, nil, nil, diags
} }
if err := s.RefreshState(); err != nil { if err := s.RefreshState(); err != nil {
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err)) diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
return nil, nil, diags return nil, nil, nil, diags
} }
// Initialize our context options // Initialize our context options
@ -58,8 +60,55 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
opts.Targets = op.Targets opts.Targets = op.Targets
opts.UIInput = op.UIIn opts.UIInput = op.UIIn
// Load the latest state. If we enter contextFromPlanFile below then the
// state snapshot in the plan file must match this, or else it'll return
// error diagnostics.
opts.State = s.State()
var tfCtx *terraform.Context
var ctxDiags tfdiags.Diagnostics
var configSnap *configload.Snapshot
if op.PlanFile != nil {
tfCtx, configSnap, ctxDiags = b.contextFromPlanFile(op.PlanFile, opts)
// Write sources into the cache of the main loader so that they are
// available if we need to generate diagnostic message snippets.
op.ConfigLoader.ImportSourcesFromSnapshot(configSnap)
} else {
tfCtx, configSnap, ctxDiags = b.contextDirect(op, opts)
}
diags = diags.Append(ctxDiags)
// If we have an operation, then we automatically do the input/validate
// here since every option requires this.
if op.Type != backend.OperationTypeInvalid {
// If input asking is enabled, then do that
if op.PlanFile == nil && b.OpInput {
mode := terraform.InputModeProvider
mode |= terraform.InputModeVar
mode |= terraform.InputModeVarUnset
inputDiags := tfCtx.Input(mode)
diags = diags.Append(inputDiags)
if inputDiags.HasErrors() {
return nil, nil, nil, diags
}
}
// If validation is enabled, validate
if b.OpValidation {
validateDiags := tfCtx.Validate()
diags = diags.Append(validateDiags)
}
}
return tfCtx, configSnap, s, diags
}
func (b *Local) contextDirect(op *backend.Operation, opts terraform.ContextOpts) (*terraform.Context, *configload.Snapshot, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Load the configuration using the caller-provided configuration loader. // Load the configuration using the caller-provided configuration loader.
config, configDiags := op.ConfigLoader.LoadConfig(op.ConfigDir) config, configSnap, configDiags := op.ConfigLoader.LoadConfigWithSnapshot(op.ConfigDir)
diags = diags.Append(configDiags) diags = diags.Append(configDiags)
if configDiags.HasErrors() { if configDiags.HasErrors() {
return nil, nil, diags return nil, nil, diags
@ -75,50 +124,80 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
opts.Variables = variables opts.Variables = variables
} }
// Load our state tfCtx, ctxDiags := terraform.NewContext(&opts)
// By the time we get here, the backend creation code in "command" took
// care of making s.State() return a state compatible with our plan,
// if any, so we can safely pass this value in both the plan context
// and new context cases below.
opts.State = s.State()
// Build the context
var tfCtx *terraform.Context
var ctxDiags tfdiags.Diagnostics
if op.Plan != nil {
tfCtx, ctxDiags = op.Plan.Context(&opts)
} else {
tfCtx, ctxDiags = terraform.NewContext(&opts)
}
diags = diags.Append(ctxDiags) diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() { return tfCtx, configSnap, diags
return nil, nil, diags }
func (b *Local) contextFromPlanFile(pf *planfile.Reader, opts terraform.ContextOpts) (*terraform.Context, *configload.Snapshot, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
const errSummary = "Invalid plan file"
// A plan file has a snapshot of configuration embedded inside it, which
// is used instead of whatever configuration might be already present
// in the filesystem.
snap, err := pf.ReadConfigSnapshot()
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
errSummary,
fmt.Sprintf("Failed to read configuration snapshot from plan file: %s.", err),
))
}
loader := configload.NewLoaderFromSnapshot(snap)
config, configDiags := loader.LoadConfig(snap.Modules[""].Dir)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, snap, diags
}
opts.Config = config
plan, err := pf.ReadPlan()
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
errSummary,
fmt.Sprintf("Failed to read plan from plan file: %s.", err),
))
} }
// If we have an operation, then we automatically do the input/validate variables := terraform.InputValues{}
// here since every option requires this. for name, dyVal := range plan.VariableValues {
if op.Type != backend.OperationTypeInvalid { ty, err := dyVal.ImpliedType()
// If input asking is enabled, then do that if err != nil {
if op.Plan == nil && b.OpInput { diags = diags.Append(tfdiags.Sourceless(
mode := terraform.InputModeProvider tfdiags.Error,
mode |= terraform.InputModeVar errSummary,
mode |= terraform.InputModeVarUnset fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err),
))
inputDiags := tfCtx.Input(mode) continue
diags = diags.Append(inputDiags) }
if inputDiags.HasErrors() { val, err := dyVal.Decode(ty)
return nil, nil, diags if err != nil {
} diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
errSummary,
fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err),
))
continue
} }
// If validation is enabled, validate variables[name] = &terraform.InputValue{
if b.OpValidation { Value: val,
validateDiags := tfCtx.Validate() SourceType: terraform.ValueFromPlan,
diags = diags.Append(validateDiags)
} }
} }
opts.Variables = variables
return tfCtx, s, diags // TODO: populate the changes (formerly diff)
// TODO: targets
// TODO: check that the states match
// TODO: impose provider SHA256 constraints
tfCtx, ctxDiags := terraform.NewContext(&opts)
diags = diags.Append(ctxDiags)
return tfCtx, snap, diags
} }
const validateWarnHeader = ` const validateWarnHeader = `

View File

@ -5,14 +5,15 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"os"
"strings" "strings"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
) )
func (b *Local) opPlan( func (b *Local) opPlan(
@ -24,8 +25,9 @@ func (b *Local) opPlan(
log.Printf("[INFO] backend/local: starting Plan operation") log.Printf("[INFO] backend/local: starting Plan operation")
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
var err error
if b.CLI != nil && op.Plan != nil { if b.CLI != nil && op.PlanFile != nil {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
"Can't re-plan a saved plan", "Can't re-plan a saved plan",
@ -56,9 +58,9 @@ func (b *Local) opPlan(
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook) b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
// Get our context // Get our context
tfCtx, opState, err := b.context(op) tfCtx, configSnap, opState, ctxDiags := b.context(op)
if err != nil { diags = diags.Append(ctxDiags)
diags = diags.Append(err) if ctxDiags.HasErrors() {
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
return return
} }
@ -67,6 +69,7 @@ func (b *Local) opPlan(
runningOp.State = tfCtx.State() runningOp.State = tfCtx.State()
// If we're refreshing before plan, perform that // If we're refreshing before plan, perform that
baseState := runningOp.State
if op.PlanRefresh { if op.PlanRefresh {
log.Printf("[INFO] backend/local: plan calling Refresh") log.Printf("[INFO] backend/local: plan calling Refresh")
@ -74,19 +77,20 @@ func (b *Local) opPlan(
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n")) b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
} }
_, err := tfCtx.Refresh() refreshedState, err := tfCtx.Refresh()
if err != nil { if err != nil {
diags = diags.Append(err) diags = diags.Append(err)
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
return return
} }
baseState = refreshedState // plan will be relative to our refreshed state
if b.CLI != nil { if b.CLI != nil {
b.CLI.Output("\n------------------------------------------------------------------------") b.CLI.Output("\n------------------------------------------------------------------------")
} }
} }
// Perform the plan in a goroutine so we can be interrupted // Perform the plan in a goroutine so we can be interrupted
var plan *terraform.Plan var plan *plans.Plan
var planDiags tfdiags.Diagnostics var planDiags tfdiags.Diagnostics
doneCh := make(chan struct{}) doneCh := make(chan struct{})
go func() { go func() {
@ -105,25 +109,19 @@ func (b *Local) opPlan(
return return
} }
// Record state // Record state
runningOp.PlanEmpty = plan.Diff.Empty() runningOp.PlanEmpty = plan.Changes.Empty()
// Save the plan to disk // Save the plan to disk
if path := op.PlanOutPath; path != "" { if path := op.PlanOutPath; path != "" {
// Write the backend if we have one plan.Backend = *op.PlanOutBackend
plan.Backend = op.PlanOutBackend
// This works around a bug (#12871) which is no longer possible to // We may have updated the state in the refresh step above, but we
// trigger but will exist for already corrupted upgrades. // will freeze that updated state in the plan file for now and
if plan.Backend != nil && plan.State != nil { // only write it if this plan is subsequently applied.
plan.State.Remote = nil plannedStateFile := statemgr.PlannedStateUpdate(opState, baseState)
}
log.Printf("[INFO] backend/local: writing plan output to: %s", path) log.Printf("[INFO] backend/local: writing plan output to: %s", path)
f, err := os.Create(path) err = planfile.Create(path, configSnap, plannedStateFile, plan)
if err == nil {
err = terraform.WritePlan(plan, f)
}
f.Close()
if err != nil { if err != nil {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
@ -137,7 +135,7 @@ func (b *Local) opPlan(
// Perform some output tasks if we have a CLI to output to. // Perform some output tasks if we have a CLI to output to.
if b.CLI != nil { if b.CLI != nil {
dispPlan := format.NewPlan(plan) dispPlan := format.NewPlan(plan.Changes)
if dispPlan.Empty() { if dispPlan.Empty() {
b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges))) b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
return return

View File

@ -9,7 +9,8 @@ import (
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -42,7 +43,7 @@ func (b *Local) opRefresh(
} }
// Get our context // Get our context
tfCtx, opState, contextDiags := b.context(op) tfCtx, _, opState, contextDiags := b.context(op)
diags = diags.Append(contextDiags) diags = diags.Append(contextDiags)
if contextDiags.HasErrors() { if contextDiags.HasErrors() {
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
@ -51,15 +52,19 @@ func (b *Local) opRefresh(
// Set our state // Set our state
runningOp.State = opState.State() runningOp.State = opState.State()
if runningOp.State.Empty() || !runningOp.State.HasResources() { if !runningOp.State.HasResources() {
if b.CLI != nil { if b.CLI != nil {
b.CLI.Output(b.Colorize().Color( diags = diags.Append(tfdiags.Sourceless(
strings.TrimSpace(refreshNoState) + "\n")) tfdiags.Warning,
"Empty or non-existent state",
"There are currently no resources tracked in the state, so there is nothing to refresh.",
))
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(refreshNoState) + "\n"))
} }
} }
// Perform the refresh in a goroutine so we can be interrupted // Perform the refresh in a goroutine so we can be interrupted
var newState *terraform.State var newState *states.State
var refreshDiags tfdiags.Diagnostics var refreshDiags tfdiags.Diagnostics
doneCh := make(chan struct{}) doneCh := make(chan struct{})
go func() { go func() {
@ -80,17 +85,12 @@ func (b *Local) opRefresh(
return return
} }
// Write and persist the state err := statemgr.WriteAndPersist(opState, newState)
if err := opState.WriteState(newState); err != nil { if err != nil {
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err)) diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
b.ReportResult(runningOp, diags) b.ReportResult(runningOp, diags)
return return
} }
if err := opState.PersistState(); err != nil {
diags = diags.Append(errwrap.Wrapf("Failed to save state: {{err}}", err))
b.ReportResult(runningOp, diags)
return
}
} }
const refreshNoState = ` const refreshNoState = `

View File

@ -10,7 +10,7 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -94,8 +94,8 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
dflt := backend.DefaultStateName dflt := backend.DefaultStateName
expectedStates := []string{dflt} expectedStates := []string{dflt}
b := New() b := &Local{}
states, err := b.States() states, err := b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -105,11 +105,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
expectedA := "test_A" expectedA := "test_A"
if _, err := b.State(expectedA); err != nil { if _, err := b.StateMgr(expectedA); err != nil {
t.Fatal(err) t.Fatal(err)
} }
states, err = b.States() states, err = b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -120,11 +120,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
expectedB := "test_B" expectedB := "test_B"
if _, err := b.State(expectedB); err != nil { if _, err := b.StateMgr(expectedB); err != nil {
t.Fatal(err) t.Fatal(err)
} }
states, err = b.States() states, err = b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -134,11 +134,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) t.Fatalf("expected %q, got %q", expectedStates, states)
} }
if err := b.DeleteState(expectedA); err != nil { if err := b.DeleteWorkspace(expectedA); err != nil {
t.Fatal(err) t.Fatal(err)
} }
states, err = b.States() states, err = b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -148,11 +148,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) t.Fatalf("expected %q, got %q", expectedStates, states)
} }
if err := b.DeleteState(expectedB); err != nil { if err := b.DeleteWorkspace(expectedB); err != nil {
t.Fatal(err) t.Fatal(err)
} }
states, err = b.States() states, err = b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -162,7 +162,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) t.Fatalf("expected %q, got %q", expectedStates, states)
} }
if err := b.DeleteState(dflt); err == nil { if err := b.DeleteWorkspace(dflt); err == nil {
t.Fatal("expected error deleting default state") t.Fatal("expected error deleting default state")
} }
} }
@ -182,14 +182,11 @@ var errTestDelegateState = errors.New("State called")
var errTestDelegateStates = errors.New("States called") var errTestDelegateStates = errors.New("States called")
var errTestDelegateDeleteState = errors.New("Delete called") var errTestDelegateDeleteState = errors.New("Delete called")
func (b *testDelegateBackend) State(name string) (state.State, error) { func (b *testDelegateBackend) State(name string) (statemgr.Full, error) {
if b.stateErr { if b.stateErr {
return nil, errTestDelegateState return nil, errTestDelegateState
} }
s := &state.LocalState{ s := statemgr.NewFilesystem("terraform.tfstate")
Path: "terraform.tfstate",
PathOut: "terraform.tfstate",
}
return s, nil return s, nil
} }
@ -216,15 +213,15 @@ func TestLocal_multiStateBackend(t *testing.T) {
deleteErr: true, deleteErr: true,
}) })
if _, err := b.State("test"); err != errTestDelegateState { if _, err := b.StateMgr("test"); err != errTestDelegateState {
t.Fatal("expected errTestDelegateState, got:", err) t.Fatal("expected errTestDelegateState, got:", err)
} }
if _, err := b.States(); err != errTestDelegateStates { if _, err := b.Workspaces(); err != errTestDelegateStates {
t.Fatal("expected errTestDelegateStates, got:", err) t.Fatal("expected errTestDelegateStates, got:", err)
} }
if err := b.DeleteState("test"); err != errTestDelegateDeleteState { if err := b.DeleteWorkspace("test"); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err) t.Fatal("expected errTestDelegateDeleteState, got:", err)
} }
} }

View File

@ -1,9 +1,13 @@
package local package local
import ( import (
"strings"
"sync" "sync"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -19,12 +23,14 @@ type CountHook struct {
ToRemove int ToRemove int
ToRemoveAndAdd int ToRemoveAndAdd int
pending map[string]countHookAction pending map[string]plans.Action
sync.Mutex sync.Mutex
terraform.NilHook terraform.NilHook
} }
var _ terraform.Hook = (*CountHook)(nil)
func (h *CountHook) Reset() { func (h *CountHook) Reset() {
h.Lock() h.Lock()
defer h.Unlock() defer h.Unlock()
@ -35,52 +41,39 @@ func (h *CountHook) Reset() {
h.Removed = 0 h.Removed = 0
} }
func (h *CountHook) PreApply( func (h *CountHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff) (terraform.HookAction, error) {
h.Lock() h.Lock()
defer h.Unlock() defer h.Unlock()
if d.Empty() {
return terraform.HookActionContinue, nil
}
if h.pending == nil { if h.pending == nil {
h.pending = make(map[string]countHookAction) h.pending = make(map[string]plans.Action)
} }
action := countHookActionChange h.pending[addr.String()] = action
if d.GetDestroy() {
action = countHookActionRemove
} else if s.ID == "" {
action = countHookActionAdd
}
h.pending[n.HumanId()] = action
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *CountHook) PostApply( func (h *CountHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, err error) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState,
e error) (terraform.HookAction, error) {
h.Lock() h.Lock()
defer h.Unlock() defer h.Unlock()
if h.pending != nil { if h.pending != nil {
if a, ok := h.pending[n.HumanId()]; ok { pendingKey := addr.String()
delete(h.pending, n.HumanId()) if action, ok := h.pending[pendingKey]; ok {
delete(h.pending, pendingKey)
if e == nil { if err == nil {
switch a { switch action {
case countHookActionAdd: case plans.Replace:
h.Added += 1 h.Added++
case countHookActionChange: h.Removed++
h.Changed += 1 case plans.Create:
case countHookActionRemove: h.Added++
h.Removed += 1 case plans.Delete:
h.Changed++
case plans.Update:
h.Removed++
} }
} }
} }
@ -89,25 +82,23 @@ func (h *CountHook) PostApply(
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *CountHook) PostDiff( func (h *CountHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
n *terraform.InstanceInfo, d *terraform.InstanceDiff) (
terraform.HookAction, error) {
h.Lock() h.Lock()
defer h.Unlock() defer h.Unlock()
// We don't count anything for data sources // We don't count anything for data resources
if strings.HasPrefix(n.Id, "data.") { if addr.Resource.Resource.Mode == addrs.DataResourceMode {
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
switch d.ChangeType() { switch action {
case terraform.DiffDestroyCreate: case plans.Replace:
h.ToRemoveAndAdd += 1 h.ToRemoveAndAdd += 1
case terraform.DiffCreate: case plans.Create:
h.ToAdd += 1 h.ToAdd += 1
case terraform.DiffDestroy: case plans.Delete:
h.ToRemove += 1 h.ToRemove += 1
case terraform.DiffUpdate: case plans.Update:
h.ToChange += 1 h.ToChange += 1
} }

View File

@ -4,6 +4,11 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -18,10 +23,14 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
"lorem": &terraform.InstanceDiff{DestroyDeposed: true}, "lorem": &terraform.InstanceDiff{DestroyDeposed: true},
} }
n := &terraform.InstanceInfo{} // TODO for k := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.DeposedKey("deadbeef"), plans.Delete, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -31,8 +40,7 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
expected.ToRemove = 1 expected.ToRemove = 1
if !reflect.DeepEqual(expected, h) { if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.", t.Fatalf("Expected %#v, got %#v instead.", expected, h)
expected, h)
} }
} }
@ -46,10 +54,14 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
"ipsum": &terraform.InstanceDiff{Destroy: true}, "ipsum": &terraform.InstanceDiff{Destroy: true},
} }
n := &terraform.InstanceInfo{} // TODO for k := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.CurrentGen, plans.Delete, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -59,8 +71,7 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
expected.ToRemove = 4 expected.ToRemove = 4
if !reflect.DeepEqual(expected, h) { if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.", t.Fatalf("Expected %#v, got %#v instead.", expected, h)
expected, h)
} }
} }
@ -85,10 +96,14 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
}, },
} }
n := &terraform.InstanceInfo{} for k := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.CurrentGen, plans.Create, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -98,8 +113,7 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
expected.ToRemove = 0 expected.ToRemove = 0
if !reflect.DeepEqual(expected, h) { if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.", t.Fatalf("Expected %#v, got %#v instead.", expected, h)
expected, h)
} }
} }
@ -127,10 +141,14 @@ func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
}, },
} }
n := &terraform.InstanceInfo{} for k := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.CurrentGen, plans.Update, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -140,32 +158,28 @@ func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
expected.ToRemove = 0 expected.ToRemove = 0
if !reflect.DeepEqual(expected, h) { if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.", t.Fatalf("Expected %#v, got %#v instead.", expected, h)
expected, h)
} }
} }
func TestCountHookPostDiff_Mixed(t *testing.T) { func TestCountHookPostDiff_Mixed(t *testing.T) {
h := new(CountHook) h := new(CountHook)
resources := map[string]*terraform.InstanceDiff{ resources := map[string]plans.Action{
"foo": &terraform.InstanceDiff{ "foo": plans.Delete,
Destroy: true, "bar": plans.NoOp,
}, "lorem": plans.Update,
"bar": &terraform.InstanceDiff{}, "ipsum": plans.Delete,
"lorem": &terraform.InstanceDiff{
Destroy: false,
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{},
},
},
"ipsum": &terraform.InstanceDiff{Destroy: true},
} }
n := &terraform.InstanceInfo{} for k, a := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.CurrentGen, a, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -190,10 +204,14 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
"ipsum": &terraform.InstanceDiff{}, "ipsum": &terraform.InstanceDiff{},
} }
n := &terraform.InstanceInfo{} for k := range resources {
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
for _, d := range resources { h.PostDiff(addr, states.CurrentGen, plans.NoOp, cty.DynamicVal, cty.DynamicVal)
h.PostDiff(n, d)
} }
expected := new(CountHook) expected := new(CountHook)
@ -211,23 +229,21 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
func TestCountHookPostDiff_DataSource(t *testing.T) { func TestCountHookPostDiff_DataSource(t *testing.T) {
h := new(CountHook) h := new(CountHook)
resources := map[string]*terraform.InstanceDiff{ resources := map[string]plans.Action{
"data.foo": &terraform.InstanceDiff{ "foo": plans.Delete,
Destroy: true, "bar": plans.NoOp,
}, "lorem": plans.Update,
"data.bar": &terraform.InstanceDiff{}, "ipsum": plans.Delete,
"data.lorem": &terraform.InstanceDiff{
Destroy: false,
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{},
},
},
"data.ipsum": &terraform.InstanceDiff{Destroy: true},
} }
for k, d := range resources { for k, a := range resources {
n := &terraform.InstanceInfo{Id: k} addr := addrs.Resource{
h.PostDiff(n, d) Mode: addrs.DataResourceMode,
Type: "test_instance",
Name: k,
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
h.PostDiff(addr, states.CurrentGen, a, cty.DynamicVal, cty.DynamicVal)
} }
expected := new(CountHook) expected := new(CountHook)

View File

@ -3,7 +3,8 @@ package local
import ( import (
"sync" "sync"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -13,21 +14,20 @@ type StateHook struct {
terraform.NilHook terraform.NilHook
sync.Mutex sync.Mutex
State state.State StateMgr statemgr.Writer
} }
func (h *StateHook) PostStateUpdate( var _ terraform.Hook = (*StateHook)(nil)
s *terraform.State) (terraform.HookAction, error) {
func (h *StateHook) PostStateUpdate(new *states.State) (terraform.HookAction, error) {
h.Lock() h.Lock()
defer h.Unlock() defer h.Unlock()
if h.State != nil { if h.StateMgr != nil {
// Write the new state if err := h.StateMgr.WriteState(new); err != nil {
if err := h.State.WriteState(s); err != nil {
return terraform.HookActionHalt, err return terraform.HookActionHalt, err
} }
} }
// Continue forth
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -12,8 +13,8 @@ func TestStateHook_impl(t *testing.T) {
} }
func TestStateHook(t *testing.T) { func TestStateHook(t *testing.T) {
is := &state.InmemState{} is := statemgr.NewTransientInMemory(nil)
var hook terraform.Hook = &StateHook{State: is} var hook terraform.Hook = &StateHook{StateMgr: is}
s := state.TestStateInitial() s := state.TestStateInitial()
action, err := hook.PostStateUpdate(s) action, err := hook.PostStateUpdate(s)

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -97,12 +97,12 @@ type TestLocalSingleState struct {
*Local *Local
} }
func (b *TestLocalSingleState) State(name string) (state.State, error) { func (b *TestLocalSingleState) State(name string) (statemgr.Full, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
return b.Local.State(name) return b.Local.StateMgr(name)
} }
func (b *TestLocalSingleState) States() ([]string, error) { func (b *TestLocalSingleState) States() ([]string, error) {

View File

@ -2,7 +2,7 @@ package backend
import ( import (
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
@ -25,15 +25,16 @@ func (Nil) Configure(cty.Value) tfdiags.Diagnostics {
return nil return nil
} }
func (Nil) State(string) (state.State, error) { func (Nil) StateMgr(string) (statemgr.Full, error) {
// We have to return a non-nil state to adhere to the interface // We must return a non-nil manager to adhere to the interface, so
return &state.InmemState{}, nil // we'll return an in-memory-only one.
return statemgr.NewFullFake(statemgr.NewTransientInMemory(nil), nil), nil
} }
func (Nil) DeleteState(string) error { func (Nil) DeleteWorkspace(string) error {
return nil return nil
} }
func (Nil) States() ([]string, error) { func (Nil) Workspaces() ([]string, error) {
return []string{DefaultStateName}, nil return []string{DefaultStateName}, nil
} }

View File

@ -82,15 +82,15 @@ func (b *Backend) configure(ctx context.Context) error {
return nil return nil
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(string) error { func (b *Backend) DeleteWorkspace(string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )

View File

@ -6,10 +6,11 @@ import (
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/azure-sdk-for-go/storage"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
) )
const ( const (
@ -18,7 +19,7 @@ const (
keyEnvPrefix = "env:" keyEnvPrefix = "env:"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
prefix := b.keyName + keyEnvPrefix prefix := b.keyName + keyEnvPrefix
params := storage.ListBlobsParameters{ params := storage.ListBlobsParameters{
Prefix: prefix, Prefix: prefix,
@ -52,7 +53,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil return result, nil
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@ -64,7 +65,7 @@ func (b *Backend) DeleteState(name string) error {
return blobReference.Delete(options) return blobReference.Delete(options)
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
client := &RemoteClient{ client := &RemoteClient{
blobClient: b.blobClient, blobClient: b.blobClient,
containerName: b.containerName, containerName: b.containerName,
@ -100,7 +101,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we have no state, we have to create an empty state // If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err) err = lockUnlock(err)
return nil, err return nil, err
} }

View File

@ -2,18 +2,19 @@ package azure
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
"encoding/base64"
"github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/azure-sdk-for-go/storage"
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid" uuid "github.com/hashicorp/go-uuid"
"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" "github.com/hashicorp/terraform/states"
) )
const ( const (
@ -162,7 +163,7 @@ func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state") log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state")
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
return "", fmt.Errorf("Failed to write empty state for locking: %s", err) return "", fmt.Errorf("Failed to write empty state for locking: %s", err)
} }
if err := stateMgr.PersistState(); err != nil { if err := stateMgr.PersistState(); err != nil {

View File

@ -6,12 +6,13 @@ package remotestate
import ( import (
"context" "context"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
) )
// Backend implements backend.Backend for remote state backends. // Backend implements backend.Backend for remote state backends.
@ -48,15 +49,15 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
return b.Backend.Configure(obj) return b.Backend.Configure(obj)
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
// This shouldn't happen // This shouldn't happen
if b.client == nil { if b.client == nil {
panic("nil remote client") panic("nil remote client")

View File

@ -7,14 +7,15 @@ import (
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
) )
const ( const (
keyEnvPrefix = "-env:" keyEnvPrefix = "-env:"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
// List our raw path // List our raw path
prefix := b.configData.Get("path").(string) + keyEnvPrefix prefix := b.configData.Get("path").(string) + keyEnvPrefix
keys, _, err := b.client.KV().Keys(prefix, "/", nil) keys, _, err := b.client.KV().Keys(prefix, "/", nil)
@ -49,7 +50,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil return result, nil
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@ -63,7 +64,7 @@ func (b *Backend) DeleteState(name string) error {
return err return err
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
// Determine the path of the data // Determine the path of the data
path := b.path(name) path := b.path(name)
@ -71,7 +72,7 @@ func (b *Backend) State(name string) (state.State, error) {
gzip := b.configData.Get("gzip").(bool) gzip := b.configData.Get("gzip").(bool)
// Build the state client // Build the state client
var stateMgr state.State = &remote.State{ var stateMgr = &remote.State{
Client: &RemoteClient{ Client: &RemoteClient{
Client: b.client, Client: b.client,
Path: path, Path: path,
@ -80,9 +81,8 @@ func (b *Backend) State(name string) (state.State, error) {
}, },
} }
// If we're not locking, disable it
if !b.lock { if !b.lock {
stateMgr = &state.LockDisabled{Inner: stateMgr} stateMgr.DisableLocks()
} }
// the default state always exists // the default state always exists
@ -117,7 +117,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we have no state, we have to create an empty state // If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err) err = lockUnlock(err)
return nil, err return nil, err
} }

View File

@ -75,15 +75,15 @@ func (b *Backend) configure(ctx context.Context) error {
return nil return nil
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(string) error { func (b *Backend) DeleteWorkspace(string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }

View File

@ -7,13 +7,14 @@ import (
"strings" "strings"
etcdv3 "github.com/coreos/etcd/clientv3" etcdv3 "github.com/coreos/etcd/clientv3"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly()) res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly())
if err != nil { if err != nil {
return nil, err return nil, err
@ -29,7 +30,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil return result, nil
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("Can't delete default state.") return fmt.Errorf("Can't delete default state.")
} }
@ -40,7 +41,7 @@ func (b *Backend) DeleteState(name string) error {
return err return err
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
var stateMgr state.State = &remote.State{ var stateMgr state.State = &remote.State{
Client: &RemoteClient{ Client: &RemoteClient{
Client: b.client, Client: b.client,
@ -73,7 +74,7 @@ func (b *Backend) State(name string) (state.State, error) {
} }
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err) err = lockUnlock(err)
return nil, err return nil, err
} }

View File

@ -7,11 +7,12 @@ import (
"strings" "strings"
"cloud.google.com/go/storage" "cloud.google.com/go/storage"
"google.golang.org/api/iterator"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
"google.golang.org/api/iterator"
) )
const ( const (
@ -19,9 +20,9 @@ const (
lockFileSuffix = ".tflock" lockFileSuffix = ".tflock"
) )
// States returns a list of names for the states found on GCS. The default // Workspaces returns a list of names for the workspaces found on GCS. The default
// state is always returned as the first element in the slice. // state is always returned as the first element in the slice.
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
states := []string{backend.DefaultStateName} states := []string{backend.DefaultStateName}
bucket := b.storageClient.Bucket(b.bucketName) bucket := b.storageClient.Bucket(b.bucketName)
@ -53,8 +54,8 @@ func (b *Backend) States() ([]string, error) {
return states, nil return states, nil
} }
// DeleteState deletes the named state. The "default" state cannot be deleted. // DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName { if name == backend.DefaultStateName {
return fmt.Errorf("cowardly refusing to delete the %q state", name) return fmt.Errorf("cowardly refusing to delete the %q state", name)
} }
@ -83,9 +84,9 @@ func (b *Backend) client(name string) (*remoteClient, error) {
}, nil }, nil
} }
// State reads and returns the named state from GCS. If the named state does // StateMgr reads and returns the named state from GCS. If the named state does
// not yet exist, a new state file is created. // not yet exist, a new state file is created.
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
c, err := b.client(name) c, err := b.client(name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,7 +128,7 @@ func (b *Backend) State(name string) (state.State, error) {
return baseErr return baseErr
} }
if err := st.WriteState(terraform.NewState()); err != nil { if err := st.WriteState(states.NewState()); err != nil {
return nil, unlock(err) return nil, unlock(err)
} }
if err := st.PersistState(); err != nil { if err := st.PersistState(); err != nil {

View File

@ -149,7 +149,7 @@ func (b *Backend) configure(ctx context.Context) error {
return nil return nil
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
@ -157,10 +157,10 @@ func (b *Backend) State(name string) (state.State, error) {
return &remote.State{Client: b.client}, nil return &remote.State{Client: b.client}, nil
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(string) error { func (b *Backend) DeleteWorkspace(string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"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" statespkg "github.com/hashicorp/terraform/states"
) )
// we keep the states and locks in package-level variables, so that they can be // we keep the states and locks in package-level variables, so that they can be
@ -87,7 +87,7 @@ func (b *Backend) configure(ctx context.Context) error {
return nil return nil
} }
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
states.Lock() states.Lock()
defer states.Unlock() defer states.Unlock()
@ -101,7 +101,7 @@ func (b *Backend) States() ([]string, error) {
return workspaces, nil return workspaces, nil
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
states.Lock() states.Lock()
defer states.Unlock() defer states.Unlock()
@ -113,7 +113,7 @@ func (b *Backend) DeleteState(name string) error {
return nil return nil
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
states.Lock() states.Lock()
defer states.Unlock() defer states.Unlock()
@ -138,7 +138,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we have no state, we have to create an empty state // If we have no state, we have to create an empty state
if v := s.State(); v == nil { if v := s.State(); v == nil {
if err := s.WriteState(terraform.NewState()); err != nil { if err := s.WriteState(statespkg.NewState()); err != nil {
return nil, err return nil, err
} }
if err := s.PersistState(); err != nil { if err := s.PersistState(); err != nil {

View File

@ -8,15 +8,16 @@ import (
"sort" "sort"
"strings" "strings"
tritonErrors "github.com/joyent/triton-go/errors"
"github.com/joyent/triton-go/storage"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
tritonErrors "github.com/joyent/triton-go/errors"
"github.com/joyent/triton-go/storage"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
result := []string{backend.DefaultStateName} result := []string{backend.DefaultStateName}
objs, err := b.storageClient.Dir().List(context.Background(), &storage.ListDirectoryInput{ objs, err := b.storageClient.Dir().List(context.Background(), &storage.ListDirectoryInput{
@ -39,7 +40,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil return result, nil
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@ -63,7 +64,7 @@ func (b *Backend) DeleteState(name string) error {
return nil return nil
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name == "" { if name == "" {
return nil, errors.New("missing state name") return nil, errors.New("missing state name")
} }
@ -103,7 +104,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we have no state, we have to create an empty state // If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err) err = lockUnlock(err)
return nil, err return nil, err
} }

View File

@ -8,13 +8,14 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"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" "github.com/hashicorp/terraform/states"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
prefix := b.workspaceKeyPrefix + "/" prefix := b.workspaceKeyPrefix + "/"
// List bucket root if there is no workspaceKeyPrefix // List bucket root if there is no workspaceKeyPrefix
@ -78,7 +79,7 @@ func (b *Backend) keyEnv(key string) string {
return parts[1] return parts[1]
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@ -111,7 +112,7 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
return client, nil return client, nil
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
client, err := b.remoteClient(name) client, err := b.remoteClient(name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -126,7 +127,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we need to force-unlock, but for some reason the state no longer // If we need to force-unlock, but for some reason the state no longer
// exists, the user will have to use aws tools to manually fix the // exists, the user will have to use aws tools to manually fix the
// situation. // situation.
existing, err := b.States() existing, err := b.Workspaces()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -167,7 +168,7 @@ func (b *Backend) State(name string) (state.State, error) {
// If we have no state, we have to create an empty state // If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil { if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil { if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err) err = lockUnlock(err)
return nil, err return nil, err
} }

View File

@ -6,15 +6,15 @@ import (
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteWorkspace(name string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName { if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }

View File

@ -5,18 +5,17 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/hcl2/hcldec"
uuid "github.com/hashicorp/go-uuid" uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
) )
// TestBackendConfig validates and configures the backend with the // TestBackendConfig validates and configures the backend with the
@ -67,31 +66,19 @@ func TestWrapConfig(raw map[string]interface{}) hcl.Body {
func TestBackendStates(t *testing.T, b Backend) { func TestBackendStates(t *testing.T, b Backend) {
t.Helper() t.Helper()
noDefault := false workspaces, err := b.Workspaces()
if _, err := b.State(DefaultStateName); err != nil { if err == ErrNamedStatesNotSupported {
if err == ErrDefaultStateNotSupported { t.Logf("TestBackend: workspaces not supported in %T, skipping", b)
noDefault = true return
} else {
t.Fatalf("error: %v", err)
}
}
states, err := b.States()
if err != nil {
if err == ErrNamedStatesNotSupported {
t.Logf("TestBackend: named states not supported in %T, skipping", b)
return
}
t.Fatalf("error: %v", err)
} }
// Test it starts with only the default // Test it starts with only the default
if !noDefault && (len(states) != 1 || states[0] != DefaultStateName) { if len(workspaces) != 1 || workspaces[0] != DefaultStateName {
t.Fatalf("should have default to start: %#v", states) t.Fatalf("should only have default to start: %#v", workspaces)
} }
// Create a couple states // Create a couple states
foo, err := b.State("foo") foo, err := b.StateMgr("foo")
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
@ -102,7 +89,7 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v) t.Fatalf("should be empty: %s", v)
} }
bar, err := b.State("bar") bar, err := b.StateMgr("bar")
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
@ -115,24 +102,10 @@ func TestBackendStates(t *testing.T, b Backend) {
// Verify they are distinct states that can be read back from storage // Verify they are distinct states that can be read back from storage
{ {
// start with a fresh state, and record the lineage being // We'll use two distinct states here and verify that changing one
// written to "bar" // does not also change the other.
barState := terraform.NewState() barState := states.NewState()
fooState := states.NewState()
// creating the named state may have created a lineage, so use that if it exists.
if s := bar.State(); s != nil && s.Lineage != "" {
barState.Lineage = s.Lineage
}
barLineage := barState.Lineage
// the foo lineage should be distinct from bar, and unchanged after
// modifying bar
fooState := terraform.NewState()
// creating the named state may have created a lineage, so use that if it exists.
if s := foo.State(); s != nil && s.Lineage != "" {
fooState.Lineage = s.Lineage
}
fooLineage := fooState.Lineage
// write a known state to foo // write a known state to foo
if err := foo.WriteState(fooState); err != nil { if err := foo.WriteState(fooState); err != nil {
@ -142,6 +115,25 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error persisting foo state:", err) t.Fatal("error persisting foo state:", err)
} }
// We'll make "bar" different by adding a fake resource state to it.
barState.SyncWrapper().SetResourceInstanceCurrent(
addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "foo",
},
}.Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte("{}"),
Status: states.ObjectReady,
SchemaVersion: 0,
},
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
// write a distinct known state to bar // write a distinct known state to bar
if err := bar.WriteState(barState); err != nil { if err := bar.WriteState(barState); err != nil {
t.Fatalf("bad: %s", err) t.Fatalf("bad: %s", err)
@ -155,17 +147,12 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing foo:", err) t.Fatal("error refreshing foo:", err)
} }
fooState = foo.State() fooState = foo.State()
switch { if fooState.HasResources() {
case fooState == nil: t.Fatal("after writing a resource to bar, foo now has resources too")
t.Fatal("nil state read from foo")
case fooState.Lineage == barLineage:
t.Fatalf("bar lineage read from foo: %#v", fooState)
case fooState.Lineage != fooLineage:
t.Fatal("foo lineage alterred")
} }
// fetch foo again from the backend // fetch foo again from the backend
foo, err = b.State("foo") foo, err = b.StateMgr("foo")
if err != nil { if err != nil {
t.Fatal("error re-fetching state:", err) t.Fatal("error re-fetching state:", err)
} }
@ -173,15 +160,12 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing foo:", err) t.Fatal("error refreshing foo:", err)
} }
fooState = foo.State() fooState = foo.State()
switch { if fooState.HasResources() {
case fooState == nil: t.Fatal("after writing a resource to bar and re-reading foo, foo now has resources too")
t.Fatal("nil state read from foo")
case fooState.Lineage != fooLineage:
t.Fatal("incorrect state returned from backend")
} }
// fetch the bar again from the backend // fetch the bar again from the backend
bar, err = b.State("bar") bar, err = b.StateMgr("bar")
if err != nil { if err != nil {
t.Fatal("error re-fetching state:", err) t.Fatal("error re-fetching state:", err)
} }
@ -189,46 +173,40 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing bar:", err) t.Fatal("error refreshing bar:", err)
} }
barState = bar.State() barState = bar.State()
switch { if !barState.HasResources() {
case barState == nil: t.Fatal("after writing a resource instance object to bar and re-reading it, the object has vanished")
t.Fatal("nil state read from bar")
case barState.Lineage != barLineage:
t.Fatal("incorrect state returned from backend")
} }
} }
// Verify we can now list them // Verify we can now list them
{ {
// we determined that named stated are supported earlier // we determined that named stated are supported earlier
states, err := b.States() workspaces, err := b.Workspaces()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
sort.Strings(states) sort.Strings(workspaces)
expected := []string{"bar", "default", "foo"} expected := []string{"bar", "default", "foo"}
if noDefault { if !reflect.DeepEqual(workspaces, expected) {
expected = []string{"bar", "foo"} t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected)
}
if !reflect.DeepEqual(states, expected) {
t.Fatalf("bad: %#v", states)
} }
} }
// Delete some states // Delete some workspaces
if err := b.DeleteState("foo"); err != nil { if err := b.DeleteWorkspace("foo"); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
// Verify the default state can't be deleted // Verify the default state can't be deleted
if err := b.DeleteState(DefaultStateName); err == nil { if err := b.DeleteWorkspace(DefaultStateName); err == nil {
t.Fatal("expected error") t.Fatal("expected error")
} }
// Create and delete the foo state again. // Create and delete the foo workspace again.
// Make sure that there are no leftover artifacts from a deleted state // Make sure that there are no leftover artifacts from a deleted state
// preventing re-creation. // preventing re-creation.
foo, err = b.State("foo") foo, err = b.StateMgr("foo")
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
@ -239,23 +217,20 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v) t.Fatalf("should be empty: %s", v)
} }
// and delete it again // and delete it again
if err := b.DeleteState("foo"); err != nil { if err := b.DeleteWorkspace("foo"); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
// Verify deletion // Verify deletion
{ {
states, err := b.States() states, err := b.Workspaces()
if err == ErrNamedStatesNotSupported { if err == ErrWorkspacesNotSupported {
t.Logf("TestBackend: named states not supported in %T, skipping", b) t.Logf("TestBackend: named states not supported in %T, skipping", b)
return return
} }
sort.Strings(states) sort.Strings(states)
expected := []string{"bar", "default"} expected := []string{"bar", "default"}
if noDefault {
expected = []string{"bar"}
}
if !reflect.DeepEqual(states, expected) { if !reflect.DeepEqual(states, expected) {
t.Fatalf("bad: %#v", states) t.Fatalf("bad: %#v", states)
} }
@ -282,7 +257,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
t.Helper() t.Helper()
// Get the default state for each // Get the default state for each
b1StateMgr, err := b1.State(DefaultStateName) b1StateMgr, err := b1.StateMgr(DefaultStateName)
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
@ -298,7 +273,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
t.Logf("TestBackend: testing state locking for %T", b1) t.Logf("TestBackend: testing state locking for %T", b1)
b2StateMgr, err := b2.State(DefaultStateName) b2StateMgr, err := b2.StateMgr(DefaultStateName)
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
@ -326,7 +301,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
// Make sure we can still get the state.State from another instance even // Make sure we can still get the state.State from another instance even
// when locked. This should only happen when a state is loaded via the // when locked. This should only happen when a state is loaded via the
// backend, and as a remote state. // backend, and as a remote state.
_, err = b2.State(DefaultStateName) _, err = b2.StateMgr(DefaultStateName)
if err != nil { if err != nil {
t.Errorf("failed to read locked state from another backend instance: %s", err) t.Errorf("failed to read locked state from another backend instance: %s", err)
} }
@ -389,10 +364,10 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
t.Fatal("client B obtained lock while held by client A") t.Fatal("client B obtained lock while held by client A")
} }
infoErr, ok := err.(*state.LockError) infoErr, ok := err.(*statemgr.LockError)
if !ok { if !ok {
unlock() unlock()
t.Fatalf("expected type *state.LockError, got : %#v", err) t.Fatalf("expected type *statemgr.LockError, got : %#v", err)
} }
// try to unlock with the second unlocker, using the ID from the error // try to unlock with the second unlocker, using the ID from the error

View File

@ -4,13 +4,13 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init" backendinit "github.com/hashicorp/terraform/backend/init"
"github.com/hashicorp/terraform/config/hcl2shim"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
) )
func dataSourceRemoteStateGetSchema() providers.Schema { func dataSourceRemoteStateGetSchema() providers.Schema {
@ -109,7 +109,7 @@ func dataSourceRemoteStateRead(d *cty.Value) (cty.Value, tfdiags.Diagnostics) {
} }
} }
state, err := b.State(name) state, err := b.StateMgr(name)
if err != nil { if err != nil {
diags = diags.Append(tfdiags.AttributeValue( diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error, tfdiags.Error,
@ -137,11 +137,10 @@ func dataSourceRemoteStateRead(d *cty.Value) (cty.Value, tfdiags.Diagnostics) {
} }
remoteState := state.State() remoteState := state.State()
if remoteState.Empty() { mod := remoteState.RootModule()
log.Println("[DEBUG] empty remote state") if mod != nil { // should always have a root module in any valid state
} else { for k, os := range mod.OutputValues {
for k, os := range remoteState.RootModule().Outputs { outputs[k] = os.Value
outputs[k] = hcl2shim.HCL2ValueFromConfigValue(os.Value)
} }
} }

View File

@ -11,9 +11,8 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -45,8 +44,7 @@ func (c *ApplyCommand) Run(args []string) int {
cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve") cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve")
} }
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
cmdFlags.IntVar( cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
@ -84,8 +82,7 @@ func (c *ApplyCommand) Run(args []string) int {
// Do a detect to determine if we need to do an init + apply. // Do a detect to determine if we need to do an init + apply.
if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf("Invalid path: %s", err))
"Invalid path: %s", err))
return 1 return 1
} else if !strings.HasPrefix(detected, "file") { } else if !strings.HasPrefix(detected, "file") {
// If this isn't a file URL then we're doing an init + // If this isn't a file URL then we're doing an init +
@ -102,39 +99,47 @@ func (c *ApplyCommand) Run(args []string) int {
} }
// Check if the path is a plan // Check if the path is a plan
plan, err := c.Plan(configPath) planFile, err := c.PlanFile(configPath)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
if c.Destroy && plan != nil { if c.Destroy && planFile != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf("Destroy can't be called with a plan file."))
"Destroy can't be called with a plan file."))
return 1 return 1
} }
if plan != nil { if planFile != nil {
// Reset the config path for backend loading // Reset the config path for backend loading
configPath = "" configPath = ""
} }
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
var backendConfig *configs.Backend // Load the backend
if plan == nil { var be backend.Enhanced
var configDiags tfdiags.Diagnostics var beDiags tfdiags.Diagnostics
backendConfig, configDiags = c.loadBackendConfig(configPath) if planFile == nil {
backendConfig, configDiags := c.loadBackendConfig(configPath)
diags = diags.Append(configDiags) diags = diags.Append(configDiags)
if configDiags.HasErrors() { if configDiags.HasErrors() {
c.showDiagnostics(diags) c.showDiagnostics(diags)
return 1 return 1
} }
}
// Load the backend be, beDiags = c.Backend(&BackendOpts{
b, beDiags := c.Backend(&BackendOpts{ Config: backendConfig,
Config: backendConfig, })
Plan: plan, } else {
}) plan, err := planFile.ReadPlan()
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to read plan from plan file",
fmt.Sprintf("Cannot read the plan from the given plan file: %s.", err),
))
}
be, beDiags = c.BackendForPlan(plan.Backend)
}
diags = diags.Append(beDiags) diags = diags.Append(beDiags)
if beDiags.HasErrors() { if beDiags.HasErrors() {
c.showDiagnostics(diags) c.showDiagnostics(diags)
@ -148,11 +153,11 @@ func (c *ApplyCommand) Run(args []string) int {
diags = nil diags = nil
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(be)
opReq.AutoApprove = autoApprove opReq.AutoApprove = autoApprove
opReq.Destroy = c.Destroy opReq.Destroy = c.Destroy
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.Plan = plan opReq.PlanFile = planFile
opReq.PlanRefresh = refresh opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypeApply opReq.Type = backend.OperationTypeApply
opReq.AutoApprove = autoApprove opReq.AutoApprove = autoApprove
@ -163,7 +168,7 @@ func (c *ApplyCommand) Run(args []string) int {
return 1 return 1
} }
op, err := c.RunOperation(b, opReq) op, err := c.RunOperation(be, opReq)
if err != nil { if err != nil {
c.showDiagnostics(err) c.showDiagnostics(err)
return 1 return 1
@ -314,26 +319,19 @@ Options:
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func outputsAsString(state *terraform.State, modPath addrs.ModuleInstance, schema []*config.Output, includeHeader bool) string { func outputsAsString(state *states.State, modPath addrs.ModuleInstance, schema map[string]*configs.Output, includeHeader bool) string {
if state == nil { if state == nil {
return "" return ""
} }
ms := state.ModuleByPath(modPath) ms := state.Module(modPath)
if ms == nil { if ms == nil {
return "" return ""
} }
outputs := ms.Outputs outputs := ms.OutputValues
outputBuf := new(bytes.Buffer) outputBuf := new(bytes.Buffer)
if len(outputs) > 0 { if len(outputs) > 0 {
schemaMap := make(map[string]*config.Output)
if schema != nil {
for _, s := range schema {
schemaMap[s.Name] = s
}
}
if includeHeader { if includeHeader {
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
} }
@ -350,23 +348,14 @@ func outputsAsString(state *terraform.State, modPath addrs.ModuleInstance, schem
sort.Strings(ks) sort.Strings(ks)
for _, k := range ks { for _, k := range ks {
schema, ok := schemaMap[k] schema, ok := schema[k]
if ok && schema.Sensitive { if ok && schema.Sensitive {
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
continue continue
} }
v := outputs[k] //v := outputs[k]
switch typedV := v.Value.(type) { outputBuf.WriteString("output printer not yet updated to use the same value formatter as 'terraform console'")
case string:
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
case []interface{}:
outputBuf.WriteString(formatListOutput("", k, typedV))
outputBuf.WriteString("\n")
case map[string]interface{}:
outputBuf.WriteString(formatMapOutput("", k, typedV))
outputBuf.WriteString("\n")
}
} }
} }

View File

@ -5,29 +5,30 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestApply_destroy(t *testing.T) { func TestApply_destroy(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()
@ -98,22 +99,20 @@ func TestApply_destroy(t *testing.T) {
} }
func TestApply_destroyLockedState(t *testing.T) { func TestApply_destroyLockedState(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
unlock, err := testLockState("./testdata", statePath) unlock, err := testLockState("./testdata", statePath)
@ -150,9 +149,7 @@ func TestApply_destroyLockedState(t *testing.T) {
} }
func TestApply_destroyPlan(t *testing.T) { func TestApply_destroyPlan(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: testModule(t, "apply"),
})
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -174,28 +171,32 @@ func TestApply_destroyPlan(t *testing.T) {
} }
func TestApply_destroyTargeted(t *testing.T) { func TestApply_destroyTargeted(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "i-ab123", AttrsJSON: []byte(`{"id":"i-ab123"}`),
}, Status: states.ObjectReady,
},
"test_load_balancer.foo": &terraform.ResourceState{
Type: "test_load_balancer",
Primary: &terraform.InstanceState{
ID: "lb-abc123",
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_load_balancer",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"i-abc123"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()

View File

@ -19,8 +19,12 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -285,8 +289,6 @@ func TestApply_defaultState(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
serial := localState.State().Serial
args := []string{ args := []string{
"-auto-approve", "-auto-approve",
testFixturePath("apply"), testFixturePath("apply"),
@ -303,10 +305,6 @@ func TestApply_defaultState(t *testing.T) {
if state == nil { if state == nil {
t.Fatal("state should not be nil") t.Fatal("state should not be nil")
} }
if state.Serial <= serial {
t.Fatalf("serial was not incremented. previous:%d, current%d", serial, state.Serial)
}
} }
func TestApply_error(t *testing.T) { func TestApply_error(t *testing.T) {
@ -499,9 +497,7 @@ func TestApply_plan(t *testing.T) {
defaultInputReader = new(bytes.Buffer) defaultInputReader = new(bytes.Buffer)
defaultInputWriter = new(bytes.Buffer) defaultInputWriter = new(bytes.Buffer)
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: testModule(t, "apply"),
})
statePath := testTempFile(t) statePath := testTempFile(t)
p := testProvider() p := testProvider()
@ -536,8 +532,7 @@ func TestApply_plan(t *testing.T) {
} }
func TestApply_plan_backup(t *testing.T) { func TestApply_plan_backup(t *testing.T) {
plan := testPlan(t) planPath := testPlanFileNoop(t)
planPath := testPlanFile(t, plan)
statePath := testTempFile(t) statePath := testTempFile(t)
backupPath := testTempFile(t) backupPath := testTempFile(t)
@ -551,7 +546,7 @@ func TestApply_plan_backup(t *testing.T) {
} }
// create a state file that needs to be backed up // create a state file that needs to be backed up
err := (&state.LocalState{Path: statePath}).WriteState(plan.State) err := statemgr.NewFilesystem(statePath).WriteState(states.NewState())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -569,7 +564,7 @@ func TestApply_plan_backup(t *testing.T) {
} }
func TestApply_plan_noBackup(t *testing.T) { func TestApply_plan_noBackup(t *testing.T) {
planPath := testPlanFile(t, testPlan(t)) planPath := testPlanFileNoop(t)
statePath := testTempFile(t) statePath := testTempFile(t)
p := testProvider() p := testProvider()
@ -620,14 +615,12 @@ func TestApply_plan_remoteState(t *testing.T) {
// Create a remote state // Create a remote state
state := testState() state := testState()
conf, srv := testRemoteState(t, state, 200) backendState, srv := testRemoteState(t, state, 200)
defer srv.Close() defer srv.Close()
state.Remote = conf testStateFileRemote(t, backendState)
planPath := testPlanFile(t, &terraform.Plan{ _, snap := testModuleWithSnapshot(t, "apply")
Config: testModule(t, "apply"), planPath := testPlanFile(t, snap, state, &plans.Plan{})
State: state,
})
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -668,9 +661,7 @@ func TestApply_planWithVarFile(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: testModule(t, "apply"),
})
statePath := testTempFile(t) statePath := testTempFile(t)
cwd, err := os.Getwd() cwd, err := os.Getwd()
@ -710,9 +701,7 @@ func TestApply_planWithVarFile(t *testing.T) {
} }
func TestApply_planVars(t *testing.T) { func TestApply_planVars(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: testModule(t, "apply"),
})
statePath := testTempFile(t) statePath := testTempFile(t)
p := testProvider() p := testProvider()
@ -743,9 +732,7 @@ func TestApply_planNoModuleFiles(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
p := testProvider() p := testProvider()
planFile := testPlanFile(t, &terraform.Plan{ planFile := testPlanFileNoop(t)
Config: testModule(t, "apply-plan-no-module"),
})
apply := &ApplyCommand{ apply := &ApplyCommand{
Meta: Meta{ Meta: Meta{
@ -763,22 +750,20 @@ func TestApply_planNoModuleFiles(t *testing.T) {
} }
func TestApply_refresh(t *testing.T) { func TestApply_refresh(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()
@ -909,22 +894,20 @@ func TestApply_shutdown(t *testing.T) {
} }
func TestApply_state(t *testing.T) { func TestApply_state(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()
@ -979,9 +962,6 @@ func TestApply_state(t *testing.T) {
backupState := testStateRead(t, statePath+DefaultBackupExtension) backupState := testStateRead(t, statePath+DefaultBackupExtension)
// nil out the ConnInfo since that should not be restored
originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil
actualStr := strings.TrimSpace(backupState.String()) actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(originalState.String()) expectedStr := strings.TrimSpace(originalState.String())
if actualStr != expectedStr { if actualStr != expectedStr {
@ -1039,62 +1019,6 @@ func TestApply_sensitiveOutput(t *testing.T) {
} }
} }
func TestApply_stateFuture(t *testing.T) {
originalState := testState()
originalState.TFVersion = "99.99.99"
statePath := testStateFile(t, originalState)
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code == 0 {
t.Fatal("should fail")
}
newState := testStateRead(t, statePath)
if !newState.Equal(originalState) {
t.Fatalf("bad: %#v", newState)
}
if newState.TFVersion != originalState.TFVersion {
t.Fatalf("bad: %#v", newState)
}
}
func TestApply_statePast(t *testing.T) {
originalState := testState()
originalState.TFVersion = "0.1.0"
statePath := testStateFile(t, originalState)
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestApply_vars(t *testing.T) { func TestApply_vars(t *testing.T) {
statePath := testTempFile(t) statePath := testTempFile(t)
@ -1285,23 +1209,20 @@ func TestApply_varFileDefaultJSON(t *testing.T) {
} }
func TestApply_backup(t *testing.T) { func TestApply_backup(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
originalState.Init() })
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
backupPath := testTempFile(t) backupPath := testTempFile(t)

View File

@ -61,7 +61,7 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor {
return nil return nil
} }
names, _ := b.States() names, _ := b.Workspaces()
return names return names
}) })
} }

View File

@ -15,6 +15,7 @@ import (
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/slowmessage" "github.com/hashicorp/terraform/helper/slowmessage"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
) )
@ -60,8 +61,8 @@ that no one else is holding a lock.
// Unlock, which is at a minimum the LockID string returned by the // Unlock, which is at a minimum the LockID string returned by the
// state.Locker. // state.Locker.
type Locker interface { type Locker interface {
// Lock the provided state, storing the reason string in the LockInfo. // Lock the provided state manager, storing the reason string in the LockInfo.
Lock(s state.State, reason string) error Lock(s statemgr.Locker, reason string) error
// Unlock the previously locked state. // Unlock the previously locked state.
// An optional error can be passed in, and will be combined with any error // An optional error can be passed in, and will be combined with any error
// from the Unlock operation. // from the Unlock operation.
@ -72,7 +73,7 @@ type locker struct {
mu sync.Mutex mu sync.Mutex
ctx context.Context ctx context.Context
timeout time.Duration timeout time.Duration
state state.State state statemgr.Locker
ui cli.Ui ui cli.Ui
color *colorstring.Colorize color *colorstring.Colorize
lockID string lockID string
@ -100,7 +101,7 @@ func NewLocker(
// Locker locks the given state and outputs to the user if locking is taking // Locker locks the given state and outputs to the user if locking is taking
// longer than the threshold. The lock is retried until the context is // longer than the threshold. The lock is retried until the context is
// cancelled. // cancelled.
func (l *locker) Lock(s state.State, reason string) error { func (l *locker) Lock(s statemgr.Locker, reason string) error {
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
@ -113,7 +114,7 @@ func (l *locker) Lock(s state.State, reason string) error {
lockInfo.Operation = reason lockInfo.Operation = reason
err := slowmessage.Do(LockThreshold, func() error { err := slowmessage.Do(LockThreshold, func() error {
id, err := state.LockWithContext(ctx, s, lockInfo) id, err := statemgr.LockWithContext(ctx, s, lockInfo)
l.lockID = id l.lockID = id
return err return err
}, func() { }, func() {
@ -165,7 +166,7 @@ func NewNoopLocker() Locker {
return noopLocker{} return noopLocker{}
} }
func (l noopLocker) Lock(state.State, string) error { func (l noopLocker) Lock(statemgr.Locker, string) error {
return nil return nil
} }

View File

@ -19,10 +19,20 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/version"
) )
// This is the directory where our test fixtures are. // This is the directory where our test fixtures are.
@ -115,6 +125,12 @@ func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr ter
func testModule(t *testing.T, name string) *configs.Config { func testModule(t *testing.T, name string) *configs.Config {
t.Helper() t.Helper()
c, _ := testModuleWithSnapshot(t, name)
return c
}
func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *configload.Snapshot) {
t.Helper()
dir := filepath.Join(fixtureDir, name) dir := filepath.Join(fixtureDir, name)
@ -131,48 +147,59 @@ func testModule(t *testing.T, name string) *configs.Config {
t.Fatal(diags.Error()) t.Fatal(diags.Error())
} }
config, diags := loader.LoadConfig(dir) config, snap, diags := loader.LoadConfigWithSnapshot(dir)
if diags.HasErrors() { if diags.HasErrors() {
t.Fatal(diags.Error()) t.Fatal(diags.Error())
} }
return config return config, snap
} }
// testPlan returns a non-nil noop plan. // testPlan returns a non-nil noop plan.
func testPlan(t *testing.T) *terraform.Plan { func testPlan(t *testing.T) *plans.Plan {
t.Helper() t.Helper()
return &plans.Plan{
state := terraform.NewState() Changes: plans.NewChanges(),
state.RootModule().Outputs["foo"] = &terraform.OutputState{
Type: "string",
Value: "foo",
}
return &terraform.Plan{
Config: testModule(t, "apply"),
State: state,
} }
} }
func testPlanFile(t *testing.T, plan *terraform.Plan) string { func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) string {
t.Helper() t.Helper()
path := testTempFile(t) stateFile := &statefile.File{
Lineage: "command.testPlanFile",
f, err := os.Create(path) State: state,
if err != nil { TerraformVersion: version.SemVer,
t.Fatalf("err: %s", err)
} }
defer f.Close()
if err := terraform.WritePlan(plan, f); err != nil { path := testTempFile(t)
t.Fatalf("err: %s", err) err := planfile.Create(path, configSnap, stateFile, plan)
if err != nil {
t.Fatalf("failed to create temporary plan file: %s", err)
} }
return path return path
} }
// testPlanFileNoop is a shortcut function that creates a plan file that
// represents no changes and returns its path. This is useful when a test
// just needs any plan file, and it doesn't matter what is inside it.
func testPlanFileNoop(t *testing.T) string {
snap := &configload.Snapshot{
Modules: map[string]*configload.SnapshotModule{
"": {
Dir: ".",
Files: map[string][]byte{
"main.tf": nil,
},
},
},
}
state := states.NewState()
plan := testPlan(t)
return testPlanFile(t, snap, state, plan)
}
func testReadPlan(t *testing.T, path string) *terraform.Plan { func testReadPlan(t *testing.T, path string) *terraform.Plan {
t.Helper() t.Helper()
@ -191,40 +218,110 @@ func testReadPlan(t *testing.T, path string) *terraform.Plan {
} }
// testState returns a test State structure that we use for a lot of tests. // testState returns a test State structure that we use for a lot of tests.
func testState() *terraform.State { func testState() *states.State {
state := &terraform.State{ return states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
Outputs: map[string]*terraform.OutputState{},
}, },
}, addrs.ProviderConfig{
} Type: "test",
state.Init() }.Absolute(addrs.RootModuleInstance),
return state )
})
} }
func testStateFile(t *testing.T, s *terraform.State) string { // writeStateForTesting is a helper that writes the given naked state to the
// given writer, generating a stub *statefile.File wrapper which is then
// immediately discarded.
func writeStateForTesting(state *states.State, w io.Writer) error {
sf := &statefile.File{
Serial: 0,
Lineage: "fake-for-testing",
State: state,
}
return statefile.Write(sf, w)
}
// testStateMgrCurrentLineage returns the current lineage for the given state
// manager, or the empty string if it does not use lineage. This is primarily
// for testing against the local backend, which always supports lineage.
func testStateMgrCurrentLineage(mgr statemgr.Persistent) string {
if pm, ok := mgr.(statemgr.PersistentMeta); ok {
m := pm.StateSnapshotMeta()
return m.Lineage
}
return ""
}
// markStateForMatching is a helper that writes a specific marker value to
// a state so that it can be recognized later with getStateMatchingMarker.
//
// Internally this just sets a root module output value called "testing_mark"
// to the given string value. If the state is being checked in other ways,
// the test code may need to compensate for the addition or overwriting of this
// special output value name.
//
// The given mark string is returned verbatim, to allow the following pattern
// in tests:
//
// mark := markStateForMatching(state, "foo")
// // (do stuff to the state)
// assertStateHasMarker(state, mark)
func markStateForMatching(state *states.State, mark string) string {
state.RootModule().SetOutputValue("testing_mark", cty.StringVal(mark), false)
return mark
}
// getStateMatchingMarker is used with markStateForMatching to retrieve the
// mark string previously added to the given state. If no such mark is present,
// the result is an empty string.
func getStateMatchingMarker(state *states.State) string {
os := state.RootModule().OutputValues["testing_mark"]
if os == nil {
return ""
}
v := os.Value
if v.Type() == cty.String && v.IsKnown() && !v.IsNull() {
return v.AsString()
}
return ""
}
// stateHasMarker is a helper around getStateMatchingMarker that also includes
// the equality test, for more convenient use in test assertion branches.
func stateHasMarker(state *states.State, want string) bool {
return getStateMatchingMarker(state) == want
}
// assertStateHasMarker wraps stateHasMarker to automatically generate a
// fatal test result (i.e. t.Fatal) if the marker doesn't match.
func assertStateHasMarker(t *testing.T, state *states.State, want string) {
if !stateHasMarker(state, want) {
t.Fatalf("wrong state marker\ngot: %q\nwant: %q", getStateMatchingMarker(state), want)
}
}
func testStateFile(t *testing.T, s *states.State) string {
t.Helper() t.Helper()
path := testTempFile(t) path := testTempFile(t)
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("failed to create temporary state file %s: %s", path, err)
} }
defer f.Close() defer f.Close()
if err := terraform.WriteState(s, f); err != nil { err = writeStateForTesting(s, f)
t.Fatalf("err: %s", err) if err != nil {
t.Fatalf("failed to write state to temporary file %s: %s", path, err)
} }
return path return path
@ -272,7 +369,7 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string {
} }
// testStateRead reads the state from a file // testStateRead reads the state from a file
func testStateRead(t *testing.T, path string) *terraform.State { func testStateRead(t *testing.T, path string) *states.State {
t.Helper() t.Helper()
f, err := os.Open(path) f, err := os.Open(path)
@ -281,12 +378,34 @@ func testStateRead(t *testing.T, path string) *terraform.State {
} }
defer f.Close() defer f.Close()
newState, err := terraform.ReadState(f) sf, err := statefile.Read(f)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
return newState return sf.State
}
// testDataStateRead reads a "data state", which is a file format resembling
// our state format v3 that is used only to track current backend settings.
//
// This old format still uses *terraform.State, but should be replaced with
// a more specialized type in a later release.
func testDataStateRead(t *testing.T, path string) *terraform.State {
t.Helper()
f, err := os.Open(path)
if err != nil {
t.Fatalf("err: %s", err)
}
defer f.Close()
s, err := terraform.ReadState(f)
if err != nil {
t.Fatalf("err: %s", err)
}
return s
} }
// testStateOutput tests that the state at the given path contains // testStateOutput tests that the state at the given path contains
@ -571,7 +690,11 @@ func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State
// testRemoteState is used to make a test HTTP server to return a given // testRemoteState is used to make a test HTTP server to return a given
// state file that can be used for testing legacy remote state. // state file that can be used for testing legacy remote state.
func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) { //
// The return values are a *terraform.State instance that should be written
// as the "data state" (really: backend state) and the server that the
// returned data state refers to.
func testRemoteState(t *testing.T, s *states.State, c int) (*terraform.State, *httptest.Server) {
t.Helper() t.Helper()
var b64md5 string var b64md5 string
@ -591,25 +714,32 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote
resp.Write(buf.Bytes()) resp.Write(buf.Bytes())
} }
retState := terraform.NewState()
srv := httptest.NewServer(http.HandlerFunc(cb)) srv := httptest.NewServer(http.HandlerFunc(cb))
remote := &terraform.RemoteState{ b := &terraform.BackendState{
Type: "http", Type: "http",
Config: map[string]string{"address": srv.URL},
} }
b.SetConfig(cty.ObjectVal(map[string]cty.Value{
"address": cty.StringVal(srv.URL),
}), &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"address": {
Type: cty.String,
Required: true,
},
},
})
retState.Backend = b
if s != nil { if s != nil {
// Set the remote data err := statefile.Write(&statefile.File{State: s}, buf)
s.Remote = remote if err != nil {
t.Fatalf("failed to write initial state: %v", err)
enc := json.NewEncoder(buf)
if err := enc.Encode(s); err != nil {
t.Fatalf("err: %v", err)
} }
md5 := md5.Sum(buf.Bytes())
b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
} }
return remote, srv return retState, srv
} }
// testlockState calls a separate process to the lock the state file at path. // testlockState calls a separate process to the lock the state file at path.

View File

@ -66,7 +66,7 @@ func (c *ConsoleCommand) Run(args []string) int {
} }
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(b)
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader() opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil { if err != nil {

View File

@ -6,9 +6,12 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
// Plan is a representation of a plan optimized for display to // Plan is a representation of a plan optimized for display to
@ -59,100 +62,63 @@ type PlanStats struct {
} }
// NewPlan produces a display-oriented Plan from a terraform.Plan. // NewPlan produces a display-oriented Plan from a terraform.Plan.
func NewPlan(plan *terraform.Plan) *Plan { func NewPlan(changes *plans.Changes) *Plan {
ret := &Plan{} ret := &Plan{}
if plan == nil || plan.Diff == nil || plan.Diff.Empty() { if changes == nil {
// Nothing to do! // Nothing to do!
return ret return ret
} }
for _, m := range plan.Diff.Modules { for _, rc := range changes.Resources {
var modulePath []string addr := rc.Addr
if !m.IsRoot() { dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
// trim off the leading "root" path segment, since it's implied
// when we use a path in a resource address. // We create "delete" actions for data resources so we can clean
modulePath = m.Path[1:] // up their entries in state, but this is an implementation detail
// that users shouldn't see.
if dataSource && rc.Action == plans.Delete {
continue
} }
for k, r := range m.Resources { // For now we'll shim this to work with our old types.
if r.Empty() { // TODO: Update for the new plan types, ideally also switching over to
continue // a structural diff renderer instead of a flat renderer.
} did := &InstanceDiff{
Addr: terraform.NewLegacyResourceInstanceAddress(addr),
}
addr, err := terraform.ParseResourceAddressForInstanceDiff(modulePath, k) switch rc.Action {
if err != nil { case plans.Create:
// should never happen; indicates invalid diff if dataSource {
panic("invalid resource address in diff") // Use "refresh" as the action for display, but core
} // currently uses Create for this internally.
// FIXME: Update core to generate plans.Read for this case
dataSource := addr.Mode == config.DataResourceMode // instead.
// We create "destroy" actions for data resources so we can clean
// up their entries in state, but this is an implementation detail
// that users shouldn't see.
if dataSource && r.ChangeType() == terraform.DiffDestroy {
continue
}
did := &InstanceDiff{
Addr: addr,
Action: r.ChangeType(),
Tainted: r.DestroyTainted,
Deposed: r.DestroyDeposed,
}
if dataSource && did.Action == terraform.DiffCreate {
// Use "refresh" as the action for display, since core
// currently uses Create for this.
did.Action = terraform.DiffRefresh did.Action = terraform.DiffRefresh
} else {
did.Action = terraform.DiffCreate
} }
case plans.Read:
ret.Resources = append(ret.Resources, did) did.Action = terraform.DiffRefresh
case plans.Delete:
if did.Action == terraform.DiffDestroy { did.Action = terraform.DiffDestroy
// Don't show any outputs for destroy actions case plans.Replace:
continue did.Action = terraform.DiffDestroyCreate
} case plans.Update:
did.Action = terraform.DiffUpdate
for k, a := range r.Attributes { default:
var action terraform.DiffChangeType panic(fmt.Sprintf("unexpected change action %s", rc.Action))
switch {
case a.NewRemoved:
action = terraform.DiffDestroy
case did.Action == terraform.DiffCreate:
action = terraform.DiffCreate
default:
action = terraform.DiffUpdate
}
did.Attributes = append(did.Attributes, &AttributeDiff{
Path: k,
Action: action,
OldValue: a.Old,
NewValue: a.New,
Sensitive: a.Sensitive,
ForcesNew: a.RequiresNew,
NewComputed: a.NewComputed,
})
}
// Sort the attributes by their paths for display
sort.Slice(did.Attributes, func(i, j int) bool {
iPath := did.Attributes[i].Path
jPath := did.Attributes[j].Path
// as a special case, "id" is always first
switch {
case iPath != jPath && (iPath == "id" || jPath == "id"):
return iPath == "id"
default:
return iPath < jPath
}
})
} }
if rc.DeposedKey != states.NotDeposed {
did.Deposed = true
}
// Since this is just a temporary stub implementation on the way
// to us replacing this with the structural diff renderer, we currently
// don't include any attributes here.
// FIXME: Implement the structural diff renderer to replace this
// codepath altogether.
} }
// Sort the instance diffs by their addresses for display. // Sort the instance diffs by their addresses for display.

View File

@ -6,14 +6,16 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
// StateOpts are the options for formatting a state. // StateOpts are the options for formatting a state.
type StateOpts struct { type StateOpts struct {
// State is the state to format. This is required. // State is the state to format. This is required.
State *terraform.State State *states.State
// Color is the colorizer. This is optional. // Color is the colorizer. This is optional.
Color *colorstring.Colorize Color *colorstring.Colorize
@ -34,7 +36,10 @@ func State(opts *StateOpts) string {
return "The state file is empty. No resources are represented." return "The state file is empty. No resources are represented."
} }
var buf bytes.Buffer // FIXME: State formatter not yet updated for new state types
return "FIXME: State formatter not yet updated for new state types"
/*var buf bytes.Buffer
buf.WriteString("[reset]") buf.WriteString("[reset]")
// Format all the modules // Format all the modules
@ -76,6 +81,7 @@ func State(opts *StateOpts) string {
} }
return opts.Color.Color(strings.TrimSpace(buf.String())) return opts.Color.Color(strings.TrimSpace(buf.String()))
*/
} }
func formatStateModuleExpand( func formatStateModuleExpand(

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
@ -46,12 +47,13 @@ func (c *GraphCommand) Run(args []string) int {
} }
// Check if the path is a plan // Check if the path is a plan
plan, err := c.Plan(configPath) var plan *plans.Plan
planFile, err := c.PlanFile(configPath)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
if plan != nil { if planFile != nil {
// Reset for backend loading // Reset for backend loading
configPath = "" configPath = ""
} }
@ -84,10 +86,10 @@ func (c *GraphCommand) Run(args []string) int {
} }
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(b)
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader() opReq.ConfigLoader, err = c.initConfigLoader()
opReq.Plan = plan opReq.PlanFile = planFile
if err != nil { if err != nil {
diags = diags.Append(err) diags = diags.Append(err)
c.showDiagnostics(diags) c.showDiagnostics(diags)

View File

@ -5,8 +5,11 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
) )
func TestGraph(t *testing.T) { func TestGraph(t *testing.T) {
@ -107,22 +110,25 @@ func TestGraph_plan(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
planPath := testPlanFile(t, &terraform.Plan{ plan := &plans.Plan{
Diff: &terraform.Diff{ Changes: plans.NewChanges(),
Modules: []*terraform.ModuleDiff{ }
&terraform.ModuleDiff{ plan.Changes.Resources = append(plan.Changes.Resources, &plans.ResourceInstanceChangeSrc{
Path: []string{"root"}, Addr: addrs.Resource{
Resources: map[string]*terraform.InstanceDiff{ Mode: addrs.ManagedResourceMode,
"test_instance.bar": &terraform.InstanceDiff{ Type: "test_instance",
Destroy: true, Name: "bar",
}, }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
}, ChangeSrc: plans.ChangeSrc{
}, Action: plans.Delete,
}, Before: plans.DynamicValue(`{}`),
After: plans.DynamicValue(`null`),
}, },
ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
Config: testModule(t, "graph"),
}) })
_, configSnap := testModuleWithSnapshot(t, "graph")
planPath := testPlanFile(t, configSnap, states.NewState(), plan)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &GraphCommand{ c := &GraphCommand{

View File

@ -10,9 +10,15 @@ import (
"time" "time"
"unicode" "unicode"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/plans"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
const defaultPeriodicUiTimer = 10 * time.Second const defaultPeriodicUiTimer = 10 * time.Second
@ -31,6 +37,8 @@ type UiHook struct {
ui cli.Ui ui cli.Ui
} }
var _ terraform.Hook = (*UiHook)(nil)
// uiResourceState tracks the state of a single resource // uiResourceState tracks the state of a single resource
type uiResourceState struct { type uiResourceState struct {
Name string Name string
@ -53,37 +61,21 @@ const (
uiResourceDestroy uiResourceDestroy
) )
func (h *UiHook) PreApply( func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
// if there's no diff, there's nothing to output
if d.Empty() {
return terraform.HookActionContinue, nil
}
id := n.HumanId()
addr := n.ResourceAddress()
op := uiResourceModify
if d.Destroy {
op = uiResourceDestroy
} else if s.ID == "" {
op = uiResourceCreate
}
var operation string var operation string
switch op { var op uiResourceOp
case uiResourceModify: switch action {
operation = "Modifying..." case plans.Delete:
case uiResourceDestroy:
operation = "Destroying..." operation = "Destroying..."
case uiResourceCreate: op = uiResourceDestroy
case plans.Create:
operation = "Creating..." operation = "Creating..."
case uiResourceUnknown: op = uiResourceCreate
return terraform.HookActionContinue, nil default:
operation = "Modifying..."
op = uiResourceModify
} }
attrBuf := new(bytes.Buffer) attrBuf := new(bytes.Buffer)
@ -92,7 +84,11 @@ func (h *UiHook) PreApply(
// determine the longest key so that we can align them all. // determine the longest key so that we can align them all.
keyLen := 0 keyLen := 0
dAttrs := d.CopyAttributes() // FIXME: This is stubbed out in preparation for rewriting it to use
// a structural presentation rather than the old-style flatmap one.
// We just assume no attributes at all for now, pending new code to
// work with the two cty.Values we are given.
dAttrs := map[string]terraform.ResourceAttrDiff{}
keys := make([]string, 0, len(dAttrs)) keys := make([]string, 0, len(dAttrs))
for key, _ := range dAttrs { for key, _ := range dAttrs {
// Skip the ID since we do that specially // Skip the ID since we do that specially
@ -109,7 +105,7 @@ func (h *UiHook) PreApply(
// Go through and output each attribute // Go through and output each attribute
for _, attrK := range keys { for _, attrK := range keys {
attrDiff, _ := d.GetAttribute(attrK) attrDiff := dAttrs[attrK]
v := attrDiff.New v := attrDiff.New
u := attrDiff.Old u := attrDiff.Old
@ -136,18 +132,16 @@ func (h *UiHook) PreApply(
} }
var stateId, stateIdSuffix string var stateId, stateIdSuffix string
if s != nil && s.ID != "" {
stateId = s.ID
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s%s[reset]%s", "[reset][bold]%s: %s%s[reset]%s",
addr, addr,
operation, operation,
stateIdSuffix, stateIdSuffix,
attrString))) attrString,
)))
id := addr.String()
uiState := uiResourceState{ uiState := uiResourceState{
Name: id, Name: id,
ResourceId: stateId, ResourceId: stateId,
@ -205,13 +199,9 @@ func (h *UiHook) stillApplying(state uiResourceState) {
} }
} }
func (h *UiHook) PostApply( func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, applyerr error) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState,
applyerr error) (terraform.HookAction, error) {
id := n.HumanId() id := addr.String()
addr := n.ResourceAddress()
h.l.Lock() h.l.Lock()
state := h.resources[id] state := h.resources[id]
@ -223,9 +213,6 @@ func (h *UiHook) PostApply(
h.l.Unlock() h.l.Unlock()
var stateIdSuffix string var stateIdSuffix string
if s != nil && s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
var msg string var msg string
switch state.Op { switch state.Op {
@ -253,31 +240,23 @@ func (h *UiHook) PostApply(
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) PreDiff( func (h *UiHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState) (terraform.HookAction, error) {
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) PreProvision( func (h *UiHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
provId string) (terraform.HookAction, error) {
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Provisioning with '%s'...[reset]", "[reset][bold]%s: Provisioning with '%s'...[reset]",
addr, provId))) addr, typeName,
)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) ProvisionOutput( func (h *UiHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, msg string) {
n *terraform.InstanceInfo,
provId string,
msg string) {
addr := n.ResourceAddress()
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(h.Colorize.Color("[reset]")) buf.WriteString(h.Colorize.Color("[reset]"))
prefix := fmt.Sprintf("%s (%s): ", addr, provId) prefix := fmt.Sprintf("%s (%s): ", addr, typeName)
s := bufio.NewScanner(strings.NewReader(msg)) s := bufio.NewScanner(strings.NewReader(msg))
s.Split(scanLines) s.Split(scanLines)
for s.Scan() { for s.Scan() {
@ -290,19 +269,10 @@ func (h *UiHook) ProvisionOutput(
h.ui.Output(strings.TrimSpace(buf.String())) h.ui.Output(strings.TrimSpace(buf.String()))
} }
func (h *UiHook) PreRefresh( func (h *UiHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s *terraform.InstanceState) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
addr := n.ResourceAddress()
var stateIdSuffix string var stateIdSuffix string
// Data resources refresh before they have ids, whereas managed
// resources are only refreshed when they have ids.
if s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Refreshing state...%s", "[reset][bold]%s: Refreshing state...%s",
@ -310,30 +280,26 @@ func (h *UiHook) PreRefresh(
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) PreImportState( func (h *UiHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
id string) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Importing from ID %q...", "[reset][bold]%s: Importing from ID %q...",
addr, id))) addr, importID,
)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) PostImportState( func (h *UiHook) PostImportState(addr addrs.AbsResourceInstance, imported []*states.ImportedObject) (terraform.HookAction, error) {
n *terraform.InstanceInfo,
s []*terraform.InstanceState) (terraform.HookAction, error) {
h.once.Do(h.init) h.once.Do(h.init)
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold][green]%s: Import complete!", addr))) "[reset][bold][green]%s: Import complete!", addr)))
for _, s := range s { for _, s := range imported {
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][green] Imported %s (ID: %s)", "[reset][green] Imported %s",
s.Ephemeral.Type, s.ID))) s.ResourceType,
)))
} }
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil

View File

@ -6,9 +6,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestUiHookPreApply_periodicTimer(t *testing.T) { func TestUiHookPreApply_periodicTimer(t *testing.T) {
@ -30,28 +35,27 @@ func TestUiHookPreApply_periodicTimer(t *testing.T) {
}, },
} }
n := &terraform.InstanceInfo{ addr := addrs.Resource{
Id: "data.aws_availability_zones.available", Mode: addrs.DataResourceMode,
ModulePath: []string{"root"}, Type: "aws_availability_zones",
Type: "aws_availability_zones", Name: "available",
} }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
s := &terraform.InstanceState{ priorState := cty.NullVal(cty.Object(map[string]cty.Type{
ID: "2017-03-05 10:56:59.298784526 +0000 UTC", "id": cty.String,
Attributes: map[string]string{ "names": cty.List(cty.String),
"id": "2017-03-05 10:56:59.298784526 +0000 UTC", }))
"names.#": "4", plannedNewState := cty.ObjectVal(map[string]cty.Value{
"names.0": "us-east-1a", "id": cty.StringVal("2017-03-05 10:56:59.298784526 +0000 UTC"),
"names.1": "us-east-1b", "names": cty.ListVal([]cty.Value{
"names.2": "us-east-1c", cty.StringVal("us-east-1a"),
"names.3": "us-east-1d", cty.StringVal("us-east-1b"),
}, cty.StringVal("us-east-1c"),
} cty.StringVal("us-east-1d"),
d := &terraform.InstanceDiff{ }),
Destroy: true, })
}
action, err := h.PreApply(n, s, d) action, err := h.PreApply(addr, states.CurrentGen, plans.Delete, priorState, plannedNewState)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -62,7 +66,7 @@ func TestUiHookPreApply_periodicTimer(t *testing.T) {
time.Sleep(3100 * time.Millisecond) time.Sleep(3100 * time.Millisecond)
// stop the background writer // stop the background writer
uiState := h.resources[n.HumanId()] uiState := h.resources[addr.String()]
close(uiState.DoneCh) close(uiState.DoneCh)
<-uiState.done <-uiState.done
@ -101,28 +105,27 @@ func TestUiHookPreApply_destroy(t *testing.T) {
}, },
} }
n := &terraform.InstanceInfo{ addr := addrs.Resource{
Id: "data.aws_availability_zones.available", Mode: addrs.DataResourceMode,
ModulePath: []string{"root"}, Type: "aws_availability_zones",
Type: "aws_availability_zones", Name: "available",
} }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
s := &terraform.InstanceState{ priorState := cty.NullVal(cty.Object(map[string]cty.Type{
ID: "2017-03-05 10:56:59.298784526 +0000 UTC", "id": cty.String,
Attributes: map[string]string{ "names": cty.List(cty.String),
"id": "2017-03-05 10:56:59.298784526 +0000 UTC", }))
"names.#": "4", plannedNewState := cty.ObjectVal(map[string]cty.Value{
"names.0": "us-east-1a", "id": cty.StringVal("2017-03-05 10:56:59.298784526 +0000 UTC"),
"names.1": "us-east-1b", "names": cty.ListVal([]cty.Value{
"names.2": "us-east-1c", cty.StringVal("us-east-1a"),
"names.3": "us-east-1d", cty.StringVal("us-east-1b"),
}, cty.StringVal("us-east-1c"),
} cty.StringVal("us-east-1d"),
d := &terraform.InstanceDiff{ }),
Destroy: true, })
}
action, err := h.PreApply(n, s, d) action, err := h.PreApply(addr, states.CurrentGen, plans.Delete, priorState, plannedNewState)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -130,6 +133,11 @@ func TestUiHookPreApply_destroy(t *testing.T) {
t.Fatalf("Expected hook to continue, given: %#v", action) t.Fatalf("Expected hook to continue, given: %#v", action)
} }
// stop the background writer
uiState := h.resources[addr.String()]
close(uiState.DoneCh)
<-uiState.done
expectedOutput := "data.aws_availability_zones.available: Destroying... (ID: 2017-03-05 10:56:59.298784526 +0000 UTC)\n" expectedOutput := "data.aws_availability_zones.available: Destroying... (ID: 2017-03-05 10:56:59.298784526 +0000 UTC)\n"
output := ui.OutputWriter.String() output := ui.OutputWriter.String()
if output != expectedOutput { if output != expectedOutput {
@ -161,12 +169,18 @@ func TestUiHookPostApply_emptyState(t *testing.T) {
}, },
} }
n := &terraform.InstanceInfo{ addr := addrs.Resource{
Id: "data.google_compute_zones.available", Mode: addrs.DataResourceMode,
ModulePath: []string{"root"}, Type: "google_compute_zones",
Type: "google_compute_zones", Name: "available",
} }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
action, err := h.PostApply(n, nil, nil)
newState := cty.NullVal(cty.Object(map[string]cty.Type{
"id": cty.String,
"names": cty.List(cty.String),
}))
action, err := h.PostApply(addr, states.CurrentGen, newState, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -7,12 +7,10 @@ import (
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax" "github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -208,7 +206,7 @@ func (c *ImportCommand) Run(args []string) int {
} }
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(b)
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader() opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil { if err != nil {

View File

@ -8,17 +8,19 @@ import (
"strings" "strings"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init" backendinit "github.com/hashicorp/terraform/backend/init"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
) )
// InitCommand is a Command implementation that takes a Terraform // InitCommand is a Command implementation that takes a Terraform
@ -266,13 +268,13 @@ func (c *InitCommand) Run(args []string) int {
} }
} }
var state *terraform.State var state *states.State
// If we have a functional backend (either just initialized or initialized // If we have a functional backend (either just initialized or initialized
// on a previous run) we'll use the current state as a potential source // on a previous run) we'll use the current state as a potential source
// of provider dependencies. // of provider dependencies.
if back != nil { if back != nil {
sMgr, err := back.State(c.Workspace()) sMgr, err := back.StateMgr(c.Workspace())
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err)) c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
return 1 return 1
@ -391,17 +393,12 @@ func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configsc
// Load the complete module tree, and fetch any missing providers. // Load the complete module tree, and fetch any missing providers.
// This method outputs its own Ui. // This method outputs its own Ui.
func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) tfdiags.Diagnostics { func (c *InitCommand) getProviders(path string, state *states.State, upgrade bool) tfdiags.Diagnostics {
config, diags := c.loadConfig(path) config, diags := c.loadConfig(path)
if diags.HasErrors() { if diags.HasErrors() {
return diags return diags
} }
if err := terraform.CheckStateVersion(state, false); err != nil {
diags = diags.Append(err)
return diags
}
var available discovery.PluginMetaSet var available discovery.PluginMetaSet
if upgrade { if upgrade {
// If we're in upgrade mode, we ignore any auto-installed plugins // If we're in upgrade mode, we ignore any auto-installed plugins

View File

@ -285,8 +285,7 @@ func TestInit_backendUnset(t *testing.T) {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
} }
s := testStateRead(t, filepath.Join( s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
DefaultDataDir, DefaultStateFilename))
if !s.Backend.Empty() { if !s.Backend.Empty() {
t.Fatal("should not have backend config") t.Fatal("should not have backend config")
} }
@ -314,7 +313,7 @@ func TestInit_backendConfigFile(t *testing.T) {
} }
// Read our saved backend config and verify we have our settings // Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -346,7 +345,7 @@ func TestInit_backendConfigFileChange(t *testing.T) {
} }
// Read our saved backend config and verify we have our settings // Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -373,7 +372,7 @@ func TestInit_backendConfigKV(t *testing.T) {
} }
// Read our saved backend config and verify we have our settings // Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -447,7 +446,7 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
} }
// Read our saved backend config and verify we have our settings // Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -460,7 +459,7 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
} }
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -489,7 +488,7 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
} }
// Read our saved backend config and verify we have our settings // Read our saved backend config and verify we have our settings
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if got, want := string(state.Backend.ConfigRaw), `{"path":"foo"}`; got != want { if got, want := string(state.Backend.ConfigRaw), `{"path":"foo"}`; got != want {
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want) t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
} }
@ -506,7 +505,7 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
} }
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename)) state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if state.Backend.Hash == backendHash { if state.Backend.Hash == backendHash {
t.Fatal("state.Backend.Hash was not updated") t.Fatal("state.Backend.Hash was not updated")

View File

@ -318,13 +318,12 @@ const (
// context with the settings from this Meta. // context with the settings from this Meta.
func (m *Meta) contextOpts() *terraform.ContextOpts { func (m *Meta) contextOpts() *terraform.ContextOpts {
var opts terraform.ContextOpts var opts terraform.ContextOpts
opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}} opts.Hooks = []terraform.Hook{m.uiHook()}
opts.Hooks = append(opts.Hooks, m.ExtraHooks...) opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
opts.Targets = m.targets opts.Targets = m.targets
opts.UIInput = m.UIInput() opts.UIInput = m.UIInput()
opts.Parallelism = m.parallelism opts.Parallelism = m.parallelism
opts.Shadow = m.shadow
// If testingOverrides are set, we'll skip the plugin discovery process // If testingOverrides are set, we'll skip the plugin discovery process
// and just work with what we've been given, thus allowing the tests // and just work with what we've been given, thus allowing the tests

View File

@ -15,16 +15,18 @@ import (
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec" "github.com/hashicorp/hcl2/hcldec"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init" backendinit "github.com/hashicorp/terraform/backend/init"
backendlocal "github.com/hashicorp/terraform/backend/local" backendlocal "github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
) )
// BackendOpts are the options used to initialize a backend.Backend. // BackendOpts are the options used to initialize a backend.Backend.
@ -38,10 +40,6 @@ type BackendOpts struct {
// arguments in Config. // arguments in Config.
ConfigOverride hcl.Body ConfigOverride hcl.Body
// Plan is a plan that is being used. If this is set, the backend
// configuration and output configuration will come from this plan.
Plan *terraform.Plan
// Init should be set to true if initialization is allowed. If this is // Init should be set to true if initialization is allowed. If this is
// false, then any configuration that requires configuration will show // false, then any configuration that requires configuration will show
// an error asking the user to reinitialize. // an error asking the user to reinitialize.
@ -78,17 +76,9 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
// local operation. // local operation.
var b backend.Backend var b backend.Backend
if !opts.ForceLocal { if !opts.ForceLocal {
// If we have a plan then, we get the the backend from there. Otherwise, var backendDiags tfdiags.Diagnostics
// the backend comes from the configuration. b, backendDiags = m.backendFromConfig(opts)
if opts.Plan != nil { diags = diags.Append(backendDiags)
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.backendFromPlan(opts)
diags = diags.Append(backendDiags)
} else {
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.backendFromConfig(opts)
diags = diags.Append(backendDiags)
}
if diags.HasErrors() { if diags.HasErrors() {
return nil, diags return nil, diags
@ -97,23 +87,9 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
log.Printf("[INFO] command: backend initialized: %T", b) log.Printf("[INFO] command: backend initialized: %T", b)
} }
// Setup the CLI opts we pass into backends that support it. // Setup the CLI opts we pass into backends that support it
cliOpts := &backend.CLIOpts{ cliOpts := m.backendCLIOpts()
CLI: m.Ui, cliOpts.Validation = true
CLIColor: m.Colorize(),
ShowDiagnostics: m.showDiagnostics,
StatePath: m.statePath,
StateOutPath: m.stateOutPath,
StateBackupPath: m.backupPath,
ContextOpts: m.contextOpts(),
Input: m.Input(),
RunningInAutomation: m.RunningInAutomation,
}
// Don't validate if we have a plan. Validation is normally harmless here,
// but validation requires interpolation, and `file()` function calls may
// not have the original files in the current execution context.
cliOpts.Validation = opts.Plan == nil
// If the backend supports CLI initialization, do it. // If the backend supports CLI initialization, do it.
if cli, ok := b.(backend.CLI); ok { if cli, ok := b.(backend.CLI); ok {
@ -151,6 +127,73 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
return local, nil return local, nil
} }
// BackendForPlan is similar to Backend, but uses backend settings that were
// stored in a plan.
//
// The current workspace name is also stored as part of the plan, and so this
// method will check that it matches the currently-selected workspace name
// and produce error diagnostics if not.
func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
f := backendinit.Backend(settings.Type)
if f == nil {
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), settings.Type))
return nil, diags
}
b := f()
schema := b.ConfigSchema()
configVal, err := settings.Config.Decode(schema.ImpliedType())
if err != nil {
diags = diags.Append(errwrap.Wrapf("saved backend configuration is invalid: {{err}}", err))
return nil, diags
}
validateDiags := b.ValidateConfig(configVal)
diags = diags.Append(validateDiags)
if validateDiags.HasErrors() {
return nil, diags
}
configureDiags := b.Configure(configVal)
diags = diags.Append(configureDiags)
// If the result of loading the backend is an enhanced backend,
// then return that as-is. This works even if b == nil (it will be !ok).
if enhanced, ok := b.(backend.Enhanced); ok {
return enhanced, nil
}
// Otherwise, we'll wrap our state-only remote backend in the local backend
// to cause any operations to be run locally.
cliOpts := m.backendCLIOpts()
cliOpts.Validation = false // don't validate here in case config contains file(...) calls where the file doesn't exist
local := &backendlocal.Local{Backend: b}
if err := local.CLIInit(cliOpts); err != nil {
// Local backend should never fail, so this is always a bug.
panic(err)
}
return local, diags
}
// backendCLIOpts returns a backend.CLIOpts object that should be passed to
// a backend that supports local CLI operations.
func (m *Meta) backendCLIOpts() *backend.CLIOpts {
return &backend.CLIOpts{
CLI: m.Ui,
CLIColor: m.Colorize(),
ShowDiagnostics: m.showDiagnostics,
StatePath: m.statePath,
StateOutPath: m.stateOutPath,
StateBackupPath: m.backupPath,
ContextOpts: m.contextOpts(),
Input: m.Input(),
RunningInAutomation: m.RunningInAutomation,
}
}
// IsLocalBackend returns true if the backend is a local backend. We use this // IsLocalBackend returns true if the backend is a local backend. We use this
// for some checks that require a remote backend. // for some checks that require a remote backend.
func (m *Meta) IsLocalBackend(b backend.Backend) bool { func (m *Meta) IsLocalBackend(b backend.Backend) bool {
@ -170,15 +213,24 @@ func (m *Meta) IsLocalBackend(b backend.Backend) bool {
// This prepares the operation. After calling this, the caller is expected // This prepares the operation. After calling this, the caller is expected
// to modify fields of the operation such as Sequence to specify what will // to modify fields of the operation such as Sequence to specify what will
// be called. // be called.
func (m *Meta) Operation() *backend.Operation { func (m *Meta) Operation(b backend.Backend) *backend.Operation {
schema := b.ConfigSchema()
workspace := m.Workspace()
planOutBackend, err := m.backendState.ForPlan(schema, workspace)
if err != nil {
// Always indicates an implementation error in practice, because
// errors here indicate invalid encoding of the backend configuration
// in memory, and we should always have validated that by the time
// we get here.
panic(fmt.Sprintf("failed to encode backend configuration for plan: %s", err))
}
return &backend.Operation{ return &backend.Operation{
PlanOutBackend: m.backendState, PlanOutBackend: planOutBackend,
Parallelism: m.parallelism,
Targets: m.targets, Targets: m.targets,
UIIn: m.UIInput(), UIIn: m.UIInput(),
UIOut: m.Ui, UIOut: m.Ui,
Variables: m.variables, Workspace: workspace,
Workspace: m.Workspace(),
LockState: m.stateLock, LockState: m.stateLock,
StateLockTimeout: m.stateLockTimeout, StateLockTimeout: m.stateLockTimeout,
} }
@ -242,9 +294,12 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
// backendFromConfig returns the initialized (not configured) backend // backendFromConfig returns the initialized (not configured) backend
// directly from the config/state.. // directly from the config/state..
// //
// This function handles any edge cases around backend config loading. For // This function handles various edge cases around backend config loading. For
// example: legacy remote state, new config changes, backend type changes, // example: new config changes, backend type changes, etc.
// etc. //
// As of the 0.12 release it can no longer migrate from legacy remote state
// to backends, and will instead instruct users to use 0.11 or earlier as
// a stepping-stone to do that migration.
// //
// This function may query the user for input unless input is disabled, in // This function may query the user for input unless input is disabled, in
// which case this function will error. // which case this function will error.
@ -288,16 +343,26 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
} }
}() }()
// This giant switch statement covers all eight possible combinations if !s.Remote.Empty() {
// of state settings between: configuring new backends, saved (previously- // Legacy remote state is no longer supported. User must first
// configured) backends, and legacy remote state. // migrate with Terraform 0.11 or earlier.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Legacy remote state not supported",
"This working directory is configured for legacy remote state, which is no longer supported from Terraform v0.12 onwards. To migrate this environment, first run \"terraform init\" under a Terraform 0.11 release, and then upgrade Terraform again.",
))
return nil, diags
}
// This switch statement covers all the different combinations of
// configuring new backends, updating previously-configured backends, etc.
switch { switch {
// No configuration set at all. Pure local state. // No configuration set at all. Pure local state.
case c == nil && s.Remote.Empty() && s.Backend.Empty(): case c == nil && s.Backend.Empty():
return nil, nil return nil, nil
// We're unsetting a backend (moving from backend => local) // We're unsetting a backend (moving from backend => local)
case c == nil && s.Remote.Empty() && !s.Backend.Empty(): case c == nil && !s.Backend.Empty():
if !opts.Init { if !opts.Init {
initReason := fmt.Sprintf( initReason := fmt.Sprintf(
"Unsetting the previously set backend %q", "Unsetting the previously set backend %q",
@ -309,30 +374,8 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
return m.backend_c_r_S(c, cHash, sMgr, true) return m.backend_c_r_S(c, cHash, sMgr, true)
// We have a legacy remote state configuration but no new backend config
case c == nil && !s.Remote.Empty() && s.Backend.Empty():
return m.backend_c_R_s(c, sMgr)
// We have a legacy remote state configuration simultaneously with a
// saved backend configuration while at the same time disabling backend
// configuration.
//
// This is a naturally impossible case: Terraform will never put you
// in this state, though it is theoretically possible through manual edits
case c == nil && !s.Remote.Empty() && !s.Backend.Empty():
if !opts.Init {
initReason := fmt.Sprintf(
"Unsetting the previously set backend %q",
s.Backend.Type)
m.backendInitRequired(initReason)
diags = diags.Append(errBackendInitRequired)
return nil, diags
}
return m.backend_c_R_S(c, cHash, sMgr)
// Configuring a backend for the first time. // Configuring a backend for the first time.
case c != nil && s.Remote.Empty() && s.Backend.Empty(): case c != nil && s.Backend.Empty():
if !opts.Init { if !opts.Init {
initReason := fmt.Sprintf( initReason := fmt.Sprintf(
"Initial configuration of the requested backend %q", "Initial configuration of the requested backend %q",
@ -345,7 +388,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
return m.backend_C_r_s(c, cHash, sMgr) return m.backend_C_r_s(c, cHash, sMgr)
// Potentially changing a backend configuration // Potentially changing a backend configuration
case c != nil && s.Remote.Empty() && !s.Backend.Empty(): case c != nil && !s.Backend.Empty():
// If our configuration is the same, then we're just initializing // If our configuration is the same, then we're just initializing
// a previously configured remote backend. // a previously configured remote backend.
if !s.Backend.Empty() { if !s.Backend.Empty() {
@ -369,237 +412,18 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
s.Backend.Hash, cHash) s.Backend.Hash, cHash)
return m.backend_C_r_S_changed(c, cHash, sMgr, true) return m.backend_C_r_S_changed(c, cHash, sMgr, true)
// Configuring a backend for the first time while having legacy
// remote state. This is very possible if a Terraform user configures
// a backend prior to ever running Terraform on an old state.
case c != nil && !s.Remote.Empty() && s.Backend.Empty():
if !opts.Init {
initReason := fmt.Sprintf(
"Initial configuration for backend %q",
c.Type)
m.backendInitRequired(initReason)
diags = diags.Append(errBackendInitRequired)
return nil, diags
}
return m.backend_C_R_s(c, sMgr)
// Configuring a backend with both a legacy remote state set
// and a pre-existing backend saved.
case c != nil && !s.Remote.Empty() && !s.Backend.Empty():
// If the hashes are the same, we have a legacy remote state with
// an unchanged stored backend state.
storedHash := s.Backend.Hash
if storedHash == cHash {
if !opts.Init {
initReason := fmt.Sprintf(
"Legacy remote state found with configured backend %q",
c.Type)
m.backendInitRequired(initReason)
diags = diags.Append(errBackendInitRequired)
return nil, diags
}
return m.backend_C_R_S_unchanged(c, sMgr, true)
}
if !opts.Init {
initReason := fmt.Sprintf(
"Reconfiguring the backend %q",
c.Type)
m.backendInitRequired(initReason)
diags = diags.Append(errBackendInitRequired)
return nil, diags
}
// We have change in all three
return m.backend_C_R_S_changed(c, sMgr)
default: default:
// This should be impossible since all state possibilties are
// tested above, but we need a default case anyways and we should
// protect against the scenario where a case is somehow removed.
diags = diags.Append(fmt.Errorf( diags = diags.Append(fmt.Errorf(
"Unhandled backend configuration state. This is a bug. Please\n"+ "Unhandled backend configuration state. This is a bug. Please\n"+
"report this error with the following information.\n\n"+ "report this error with the following information.\n\n"+
"Config Nil: %v\n"+ "Config Nil: %v\n"+
"Saved Backend Empty: %v\n"+ "Saved Backend Empty: %v\n",
"Legacy Remote Empty: %v\n", c == nil, s.Backend.Empty(),
c == nil, s.Backend.Empty(), s.Remote.Empty(),
)) ))
return nil, diags return nil, diags
} }
} }
// backendFromPlan loads the backend from a given plan file.
func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
if opts.Plan == nil {
panic("plan should not be nil")
}
var diags tfdiags.Diagnostics
// We currently don't allow "-state" to be specified.
if m.statePath != "" {
diags = diags.Append(fmt.Errorf(
"State path cannot be specified with a plan file. The plan itself contains\n" +
"the state to use. If you wish to change that, please create a new plan\n" +
"and specify the state path when creating the plan.",
))
}
planBackend := opts.Plan.Backend
planState := opts.Plan.State
if planState == nil {
// The state can be nil, we just have to make it empty for the logic
// in this function.
planState = terraform.NewState()
}
// Validation only for non-local plans
local := planState.Remote.Empty() && planBackend.Empty()
if !local {
// We currently don't allow "-state-out" to be specified.
if m.stateOutPath != "" {
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanStateFlag)))
return nil, diags
}
}
// If we have a stateOutPath, we must also specify it as the
// input path so we can check it properly. We restore it after this
// function exits.
original := m.statePath
m.statePath = m.stateOutPath
defer func() { m.statePath = original }()
var b backend.Backend
switch {
// No remote state at all, all local
case planState.Remote.Empty() && planBackend.Empty():
log.Printf("[INFO] command: initializing local backend from plan (not set)")
// Get the local backend
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.Backend(&BackendOpts{ForceLocal: true})
diags = diags.Append(backendDiags)
// New backend configuration set
case planState.Remote.Empty() && !planBackend.Empty():
log.Printf(
"[INFO] command: initializing backend from plan: %s",
planBackend.Type)
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.backendInitFromSaved(planBackend)
diags = diags.Append(backendDiags)
// Legacy remote state set
case !planState.Remote.Empty() && planBackend.Empty():
log.Printf(
"[INFO] command: initializing legacy remote backend from plan: %s",
planState.Remote.Type)
// Write our current state to an inmemory state just so that we
// have it in the format of state.State
inmem := &state.InmemState{}
inmem.WriteState(planState)
// Get the backend through the normal means of legacy state
var moreDiags tfdiags.Diagnostics
b, moreDiags = m.backend_c_R_s(nil, inmem)
diags = diags.Append(moreDiags)
// Both set, this can't happen in a plan.
case !planState.Remote.Empty() && !planBackend.Empty():
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanBoth)))
return nil, diags
}
// If we had an error, return that
if diags.HasErrors() {
return nil, diags
}
env := m.Workspace()
// Get the state so we can determine the effect of using this plan
realMgr, err := b.State(env)
if err != nil {
diags = diags.Append(fmt.Errorf("Error reading state: %s", err))
return nil, diags
}
if m.stateLock {
stateLocker := clistate.NewLocker(context.Background(), m.stateLockTimeout, m.Ui, m.Colorize())
if err := stateLocker.Lock(realMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
return nil, diags
}
defer stateLocker.Unlock(nil)
}
if err := realMgr.RefreshState(); err != nil {
diags = diags.Append(fmt.Errorf("Error reading state: %s", err))
return nil, diags
}
real := realMgr.State()
if real != nil {
// If they're not the same lineage, don't allow this
if !real.SameLineage(planState) {
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanLineageDiff)))
return nil, diags
}
// Compare ages
comp, err := real.CompareAges(planState)
if err != nil {
diags = diags.Append(fmt.Errorf("Error comparing state ages for safety: %s", err))
return nil, diags
}
switch comp {
case terraform.StateAgeEqual:
// State ages are equal, this is perfect
case terraform.StateAgeReceiverOlder:
// Real state is somehow older, this is okay.
case terraform.StateAgeReceiverNewer:
// If we have an older serial it is a problem but if we have a
// differing serial but are still identical, just let it through.
if real.Equal(planState) {
log.Printf("[WARN] command: state in plan has older serial, but Equal is true")
break
}
// The real state is newer, this is not allowed.
diags = diags.Append(fmt.Errorf(
strings.TrimSpace(errBackendPlanOlder),
planState.Serial, real.Serial,
))
return nil, diags
}
}
// Write the state
newState := opts.Plan.State.DeepCopy()
if newState != nil {
newState.Remote = nil
newState.Backend = nil
}
// realMgr locked above
if err := realMgr.WriteState(newState); err != nil {
diags = diags.Append(fmt.Errorf("Error writing state: %s", err))
return nil, diags
}
if err := realMgr.PersistState(); err != nil {
diags = diags.Append(fmt.Errorf("Error writing state: %s", err))
return nil, diags
}
return b, diags
}
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Backend Config Scenarios // Backend Config Scenarios
// //
@ -618,7 +442,7 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, tfdiags.Diag
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Unconfiguring a backend (moving from backend => local). // Unconfiguring a backend (moving from backend => local).
func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
s := sMgr.State() s := sMgr.State()
// Get the backend type for output // Get the backend type for output
@ -673,7 +497,7 @@ func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr state.State, ou
} }
// Legacy remote state // Legacy remote state
func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n") m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
@ -683,7 +507,7 @@ func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr state.State) (backend.Back
} }
// Unsetting backend, saved backend, legacy remote state // Unsetting backend, saved backend, legacy remote state
func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n") m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
@ -693,7 +517,7 @@ func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr state.State) (b
} }
// Configuring a backend for the first time with legacy remote state. // Configuring a backend for the first time with legacy remote state.
func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n") m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
@ -703,7 +527,7 @@ func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr state.State) (backend.Back
} }
// Configuring a backend for the first time. // Configuring a backend for the first time.
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
// Get the backend // Get the backend
b, configVal, diags := m.backendInitFromConfig(c) b, configVal, diags := m.backendInitFromConfig(c)
if diags.HasErrors() { if diags.HasErrors() {
@ -717,7 +541,9 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (b
return nil, diags return nil, diags
} }
workspaces, err := localB.States() workspace := m.Workspace()
localState, err := localB.StateMgr(workspace)
if err != nil { if err != nil {
diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
return nil, diags return nil, diags
@ -809,7 +635,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (b
} }
// Changing a previously saved backend. // Changing a previously saved backend.
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
if output { if output {
// Notify the user // Notify the user
m.Ui.Output(m.Colorize().Color(fmt.Sprintf( m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
@ -894,7 +720,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr state.S
} }
// Initiailizing an unchanged saved backend // Initiailizing an unchanged saved backend
func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
s := sMgr.State() s := sMgr.State()
@ -948,7 +774,7 @@ func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr state
} }
// Initiailizing a changed saved backend with legacy remote state. // Initiailizing a changed saved backend with legacy remote state.
func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n") m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
@ -958,7 +784,7 @@ func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr state.State) (back
} }
// Initiailizing an unchanged saved backend with legacy remote state. // Initiailizing an unchanged saved backend with legacy remote state.
func (m *Meta) backend_C_R_S_unchanged(c *configs.Backend, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) { func (m *Meta) backend_C_R_S_unchanged(c *configs.Backend, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n") m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")

View File

@ -11,6 +11,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
@ -43,8 +46,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
// We need to check what the named state status is. If we're converting // We need to check what the named state status is. If we're converting
// from multi-state to single-state for example, we need to handle that. // from multi-state to single-state for example, we need to handle that.
var oneSingle, twoSingle bool var oneSingle, twoSingle bool
oneStates, err := opts.One.States() oneStates, err := opts.One.Workspaces()
if err == backend.ErrNamedStatesNotSupported { if err == backend.ErrWorkspacesNotSupported {
oneSingle = true oneSingle = true
err = nil err = nil
} }
@ -53,8 +56,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
errMigrateLoadStates), opts.OneType, err) errMigrateLoadStates), opts.OneType, err)
} }
_, err = opts.Two.States() _, err = opts.Two.Workspaces()
if err == backend.ErrNamedStatesNotSupported { if err == backend.ErrWorkspacesNotSupported {
twoSingle = true twoSingle = true
err = nil err = nil
} }
@ -144,7 +147,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
} }
// Read all the states // Read all the states
oneStates, err := opts.One.States() oneStates, err := opts.One.Workspaces()
if err != nil { if err != nil {
return fmt.Errorf(strings.TrimSpace( return fmt.Errorf(strings.TrimSpace(
errMigrateLoadStates), opts.OneType, err) errMigrateLoadStates), opts.OneType, err)
@ -260,7 +263,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
// Single state to single state, assumed default state name. // Single state to single state, assumed default state name.
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
stateOne, err := opts.One.State(opts.oneEnv) stateOne, err := opts.One.StateMgr(opts.oneEnv)
if err != nil { if err != nil {
return fmt.Errorf(strings.TrimSpace( return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err) errMigrateSingleLoadDefault), opts.OneType, err)
@ -270,47 +273,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
errMigrateSingleLoadDefault), opts.OneType, err) errMigrateSingleLoadDefault), opts.OneType, err)
} }
// Do not migrate workspaces without state. stateTwo, err := opts.Two.StateMgr(opts.twoEnv)
if stateOne.State() == nil {
return nil
}
stateTwo, err := opts.Two.State(opts.twoEnv)
if err == backend.ErrDefaultStateNotSupported {
// If the backend doesn't support using the default state, we ask the user
// for a new name and migrate the default state to the given named state.
stateTwo, err = func() (state.State, error) {
name, err := m.UIInput().Input(&terraform.InputOpts{
Id: "new-state-name",
Query: fmt.Sprintf(
"[reset][bold][yellow]The %q backend configuration only allows "+
"named workspaces![reset]",
opts.TwoType),
Description: strings.TrimSpace(inputBackendNewWorkspaceName),
})
if err != nil {
return nil, fmt.Errorf("Error asking for new state name: %s", err)
}
// Update the name of the target state.
opts.twoEnv = name
stateTwo, err := opts.Two.State(opts.twoEnv)
if err != nil {
return nil, err
}
// If the currently selected workspace is the default workspace, then set
// the named workspace as the new selected workspace.
if m.Workspace() == backend.DefaultStateName {
if err := m.SetWorkspace(opts.twoEnv); err != nil {
return nil, fmt.Errorf("Failed to set new workspace: %s", err)
}
}
return stateTwo, nil
}()
}
if err != nil { if err != nil {
return fmt.Errorf(strings.TrimSpace( return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.TwoType, err) errMigrateSingleLoadDefault), opts.TwoType, err)
@ -328,8 +291,15 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
// no reason to migrate if the state is already there // no reason to migrate if the state is already there
if one.Equal(two) { if one.Equal(two) {
// Equal isn't identical; it doesn't check lineage. // Equal isn't identical; it doesn't check lineage.
if one != nil && two != nil && one.Lineage == two.Lineage { sm1, _ := stateOne.(statemgr.PersistentMeta)
return nil sm2, _ := stateTwo.(statemgr.PersistentMeta)
if one != nil && two != nil {
if sm1 == nil || sm2 == nil {
return nil
}
if sm1.StateSnapshotMeta().Lineage == sm2.StateSnapshotMeta().Lineage {
return nil
}
} }
} }
@ -363,15 +333,6 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
two = stateTwo.State() two = stateTwo.State()
} }
// Clear the legacy remote state in both cases. If we're at the migration
// step then this won't be used anymore.
if one != nil {
one.Remote = nil
}
if two != nil {
two.Remote = nil
}
var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error) var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
switch { switch {
// No migration necessary // No migration necessary
@ -453,14 +414,9 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
defer os.RemoveAll(td) defer os.RemoveAll(td)
// Helper to write the state // Helper to write the state
saveHelper := func(n, path string, s *terraform.State) error { saveHelper := func(n, path string, s *states.State) error {
f, err := os.Create(path) mgr := statemgr.NewFilesystem(path)
if err != nil { return mgr.WriteState(s)
return err
}
defer f.Close()
return terraform.WriteState(s, f)
} }
// Write the states // Write the states

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,11 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -129,51 +130,23 @@ func (m *Meta) Config(path string) (*config.Config, error) {
return c, nil return c, nil
} }
// Plan returns the plan for the given path. // PlanFile returns a reader for the plan file at the given path.
// //
// This only has an effect if the path itself looks like a plan. // If the return value and error are both nil, the given path exists but seems
// If error is nil and the plan is nil, then the path didn't look like // to be a configuration directory instead.
// a plan.
// //
// Error will be non-nil if path looks like a plan and loading the plan // Error will be non-nil if path refers to something which looks like a plan
// failed. // file and loading the file fails.
func (m *Meta) Plan(path string) (*terraform.Plan, error) { func (m *Meta) PlanFile(path string) (*planfile.Reader, error) {
// Open the path no matter if its a directory or file fi, err := os.Stat(path)
f, err := os.Open(path)
defer f.Close()
if err != nil {
return nil, fmt.Errorf(
"Failed to load Terraform configuration or plan: %s", err)
}
// Stat it so we can check if its a directory
fi, err := f.Stat()
if err != nil {
return nil, fmt.Errorf(
"Failed to load Terraform configuration or plan: %s", err)
}
// If this path is a directory, then it can't be a plan. Not an error.
if fi.IsDir() {
return nil, nil
}
// Read the plan
p, err := terraform.ReadPlan(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// We do a validation here that seems odd but if any plan is given, if fi.IsDir() {
// we must not have set any extra variables. The plan itself contains // Looks like a configuration directory.
// the variables and those aren't overwritten. return nil, nil
if len(m.variableArgs.AllItems()) > 0 {
return nil, fmt.Errorf(
"You can't set variables with the '-var' or '-var-file' flag\n" +
"when you're applying a plan file. The variables used when\n" +
"the plan was created will be used. If you wish to use different\n" +
"variable values, create a new plan file.")
} }
return p, nil return planfile.Open(path)
} }

View File

@ -2,14 +2,15 @@ package command
import ( import (
"bytes" "bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -63,7 +64,7 @@ func (c *OutputCommand) Run(args []string) int {
env := c.Workspace() env := c.Workspace()
// Get the state // Get the state
stateStore, err := b.State(env) stateStore, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -74,13 +75,15 @@ func (c *OutputCommand) Run(args []string) int {
return 1 return 1
} }
// This command uses a legacy shorthand syntax for the module path that moduleAddr, addrDiags := addrs.ParseModuleInstanceStr(module)
// can't deal with keyed instances, so we'll just shim it for now and diags = diags.Append(addrDiags)
// make the breaking change for this interface later. if addrDiags.HasErrors() {
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim() c.showDiagnostics(diags)
return 1
}
state := stateStore.State() state := stateStore.State()
mod := state.ModuleByPath(modPath) mod := state.Module(moduleAddr)
if mod == nil { if mod == nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"The module %s could not be found. There is nothing to output.", "The module %s could not be found. There is nothing to output.",
@ -88,7 +91,12 @@ func (c *OutputCommand) Run(args []string) int {
return 1 return 1
} }
if !jsonOutput && (state.Empty() || len(mod.Outputs) == 0) { // TODO: We need to do an eval walk here to make sure all of the output
// values recorded in the state are up-to-date.
c.Ui.Error("output command not yet updated to do eval walk")
return 1
if !jsonOutput && (state.Empty() || len(mod.OutputValues) == 0) {
c.Ui.Error( c.Ui.Error(
"The state file either has no outputs defined, or all the defined\n" + "The state file either has no outputs defined, or all the defined\n" +
"outputs are empty. Please define an output in your configuration\n" + "outputs are empty. Please define an output in your configuration\n" +
@ -101,7 +109,12 @@ func (c *OutputCommand) Run(args []string) int {
if name == "" { if name == "" {
if jsonOutput { if jsonOutput {
jsonOutputs, err := json.MarshalIndent(mod.Outputs, "", " ") vals := make(map[string]cty.Value, len(mod.OutputValues))
for n, os := range mod.OutputValues {
vals[n] = os.Value
}
valsObj := cty.ObjectVal(vals)
jsonOutputs, err := ctyjson.Marshal(valsObj, valsObj.Type())
if err != nil { if err != nil {
return 1 return 1
} }
@ -109,12 +122,12 @@ func (c *OutputCommand) Run(args []string) int {
c.Ui.Output(string(jsonOutputs)) c.Ui.Output(string(jsonOutputs))
return 0 return 0
} else { } else {
c.Ui.Output(outputsAsString(state, modPath, nil, false)) c.Ui.Output(outputsAsString(state, moduleAddr, nil, false))
return 0 return 0
} }
} }
v, ok := mod.Outputs[name] os, ok := mod.OutputValues[name]
if !ok { if !ok {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"The output variable requested could not be found in the state\n" + "The output variable requested could not be found in the state\n" +
@ -123,29 +136,18 @@ func (c *OutputCommand) Run(args []string) int {
"with new output variables until that command is run.")) "with new output variables until that command is run."))
return 1 return 1
} }
v := os.Value
if jsonOutput { if jsonOutput {
jsonOutputs, err := json.MarshalIndent(v, "", " ") jsonOutput, err := ctyjson.Marshal(v, v.Type())
if err != nil { if err != nil {
return 1 return 1
} }
c.Ui.Output(string(jsonOutputs)) c.Ui.Output(string(jsonOutput))
} else { } else {
switch output := v.Value.(type) { c.Ui.Error("TODO: update output command to use the same value renderer as the console")
case string: return 1
c.Ui.Output(output)
return 0
case []interface{}:
c.Ui.Output(formatListOutput("", "", output))
return 0
case map[string]interface{}:
c.Ui.Output(formatMapOutput("", "", output))
return 0
default:
c.Ui.Error(fmt.Sprintf("Unknown output type: %T", v.Type))
return 1
}
} }
return 0 return 0

View File

@ -6,24 +6,21 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
) )
func TestOutput(t *testing.T) { func TestOutput(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", })
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -50,28 +47,18 @@ func TestOutput(t *testing.T) {
} }
func TestModuleOutput(t *testing.T) { func TestModuleOutput(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", s.SetOutputValue(
Type: "string", addrs.OutputValue{Name: "blah"}.Absolute(addrs.Module{"my_module"}.UnkeyedInstanceShim()),
}, cty.StringVal("tastatur"),
}, false,
}, )
{ })
Path: []string{"root", "my_module"},
Outputs: map[string]*terraform.OutputState{
"blah": {
Value: "tastatur",
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -100,28 +87,18 @@ func TestModuleOutput(t *testing.T) {
} }
func TestModuleOutputs(t *testing.T) { func TestModuleOutputs(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", s.SetOutputValue(
Type: "string", addrs.OutputValue{Name: "blah"}.Absolute(addrs.Module{"my_module"}.UnkeyedInstanceShim()),
}, cty.StringVal("tastatur"),
}, false,
}, )
{ })
Path: []string{"root", "my_module"},
Outputs: map[string]*terraform.OutputState{
"blah": {
Value: "tastatur",
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -149,28 +126,21 @@ func TestModuleOutputs(t *testing.T) {
} }
func TestOutput_nestedListAndMap(t *testing.T) { func TestOutput_nestedListAndMap(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.ListVal([]cty.Value{
Outputs: map[string]*terraform.OutputState{ cty.MapVal(map[string]cty.Value{
"foo": { "key": cty.StringVal("value"),
Value: []interface{}{ "key2": cty.StringVal("value2"),
map[string]interface{}{ }),
"key": "value", cty.MapVal(map[string]cty.Value{
"key2": "value2", "key": cty.StringVal("value"),
}, }),
map[string]interface{}{ }),
"key": "value", false,
}, )
}, })
Type: "list",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -196,19 +166,13 @@ func TestOutput_nestedListAndMap(t *testing.T) {
} }
func TestOutput_json(t *testing.T) { func TestOutput_json(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", })
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -236,15 +200,7 @@ func TestOutput_json(t *testing.T) {
} }
func TestOutput_emptyOutputsErr(t *testing.T) { func TestOutput_emptyOutputsErr(t *testing.T) {
originalState := &terraform.State{ originalState := states.NewState()
Modules: []*terraform.ModuleState{
{
Path: []string{"root"},
Outputs: map[string]*terraform.OutputState{},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()
@ -265,15 +221,7 @@ func TestOutput_emptyOutputsErr(t *testing.T) {
} }
func TestOutput_jsonEmptyOutputs(t *testing.T) { func TestOutput_jsonEmptyOutputs(t *testing.T) {
originalState := &terraform.State{ originalState := states.NewState()
Modules: []*terraform.ModuleState{
{
Path: []string{"root"},
Outputs: map[string]*terraform.OutputState{},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
p := testProvider() p := testProvider()
@ -301,20 +249,13 @@ func TestOutput_jsonEmptyOutputs(t *testing.T) {
} }
func TestMissingModuleOutput(t *testing.T) { func TestMissingModuleOutput(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", })
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -337,20 +278,13 @@ func TestMissingModuleOutput(t *testing.T) {
} }
func TestOutput_badVar(t *testing.T) { func TestOutput_badVar(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", })
Type: "string",
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -371,24 +305,18 @@ func TestOutput_badVar(t *testing.T) {
} }
func TestOutput_blank(t *testing.T) { func TestOutput_blank(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", s.SetOutputValue(
Type: "string", addrs.OutputValue{Name: "name"}.Absolute(addrs.RootModuleInstance),
}, cty.StringVal("john-doe"),
"name": { false,
Value: "john-doe", )
Type: "string", })
},
},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -449,7 +377,7 @@ func TestOutput_noArgs(t *testing.T) {
} }
func TestOutput_noState(t *testing.T) { func TestOutput_noState(t *testing.T) {
originalState := &terraform.State{} originalState := states.NewState()
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -470,14 +398,7 @@ func TestOutput_noState(t *testing.T) {
} }
func TestOutput_noVars(t *testing.T) { func TestOutput_noVars(t *testing.T) {
originalState := &terraform.State{ originalState := states.NewState()
Modules: []*terraform.ModuleState{
{
Path: []string{"root"},
Outputs: map[string]*terraform.OutputState{},
},
},
}
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -499,19 +420,13 @@ func TestOutput_noVars(t *testing.T) {
} }
func TestOutput_stateDefault(t *testing.T) { func TestOutput_stateDefault(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetOutputValue(
{ addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root"}, cty.StringVal("bar"),
Outputs: map[string]*terraform.OutputState{ false,
"foo": { )
Value: "bar", })
Type: "string",
},
},
},
},
}
// Write the state file in a temporary directory with the // Write the state file in a temporary directory with the
// default filename. // default filename.
@ -522,7 +437,7 @@ func TestOutput_stateDefault(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
err = terraform.WriteState(originalState, f) err = writeStateForTesting(originalState, f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)

View File

@ -53,37 +53,35 @@ func (c *PlanCommand) Run(args []string) int {
return 1 return 1
} }
// Check if the path is a plan // Check if the path is a plan, which is not permitted
plan, err := c.Plan(configPath) planFileReader, err := c.PlanFile(configPath)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
if plan != nil { if planFileReader != nil {
// Disable refreshing no matter what since we only want to show the plan c.showDiagnostics(tfdiags.Sourceless(
refresh = false tfdiags.Error,
"Invalid configuration directory",
// Set the config path to empty for backend loading fmt.Sprintf("Cannot pass a saved plan file to the 'terraform plan' command. To apply a saved plan, use: terraform apply %s", configPath),
configPath = "" ))
return 1
} }
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
var backendConfig *configs.Backend var backendConfig *configs.Backend
if plan == nil { var configDiags tfdiags.Diagnostics
var configDiags tfdiags.Diagnostics backendConfig, configDiags = c.loadBackendConfig(configPath)
backendConfig, configDiags = c.loadBackendConfig(configPath) diags = diags.Append(configDiags)
diags = diags.Append(configDiags) if configDiags.HasErrors() {
if configDiags.HasErrors() { c.showDiagnostics(diags)
c.showDiagnostics(diags) return 1
return 1
}
} }
// Load the backend // Load the backend
b, backendDiags := c.Backend(&BackendOpts{ b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig, Config: backendConfig,
Plan: plan,
}) })
diags = diags.Append(backendDiags) diags = diags.Append(backendDiags)
if backendDiags.HasErrors() { if backendDiags.HasErrors() {
@ -97,10 +95,10 @@ func (c *PlanCommand) Run(args []string) int {
diags = nil diags = nil
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(b)
opReq.Destroy = destroy opReq.Destroy = destroy
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.Plan = plan opReq.PlanRefresh = refresh
opReq.PlanOutPath = outPath opReq.PlanOutPath = outPath
opReq.PlanRefresh = refresh opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypePlan opReq.Type = backend.OperationTypePlan

View File

@ -11,7 +11,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -83,9 +85,7 @@ func TestPlan_plan(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: testModule(t, "apply"),
})
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -107,22 +107,20 @@ func TestPlan_plan(t *testing.T) {
} }
func TestPlan_destroy(t *testing.T) { func TestPlan_destroy(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
outPath := testTempFile(t) outPath := testTempFile(t)
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
@ -232,21 +230,20 @@ func TestPlan_outPath(t *testing.T) {
} }
func TestPlan_outPathNoChange(t *testing.T) { func TestPlan_outPathNoChange(t *testing.T) {
originalState := &terraform.State{ originalState := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
td := testTempDir(t) td := testTempDir(t)
@ -337,67 +334,6 @@ func TestPlan_outBackend(t *testing.T) {
} }
} }
// When using "-out" with a legacy remote state, the plan should encode
// the backend config
func TestPlan_outBackendLegacy(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
copy.CopyDir(testFixturePath("plan-out-backend-legacy"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Our state
originalState := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
},
},
}
originalState.Init()
// Setup our legacy state
remoteState, srv := testRemoteState(t, originalState, 200)
defer srv.Close()
dataState := terraform.NewState()
dataState.Remote = remoteState
testStateFileRemote(t, dataState)
outPath := "foo"
p := testProvider()
ui := new(cli.MockUi)
c := &PlanCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-out", outPath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
plan := testReadPlan(t, outPath)
if !plan.Diff.Empty() {
t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
}
if plan.State.Remote.Empty() {
t.Fatal("should have remote info")
}
}
func TestPlan_refresh(t *testing.T) { func TestPlan_refresh(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -492,70 +428,6 @@ func TestPlan_stateDefault(t *testing.T) {
} }
} }
func TestPlan_stateFuture(t *testing.T) {
originalState := testState()
originalState.TFVersion = "99.99.99"
statePath := testStateFile(t, originalState)
p := testProvider()
ui := new(cli.MockUi)
c := &PlanCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
testFixturePath("plan"),
}
if code := c.Run(args); code == 0 {
t.Fatal("should fail")
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
if !newState.Equal(originalState) {
t.Fatalf("bad: %#v", newState)
}
if newState.TFVersion != originalState.TFVersion {
t.Fatalf("bad: %#v", newState)
}
}
func TestPlan_statePast(t *testing.T) {
originalState := testState()
originalState.TFVersion = "0.1.0"
statePath := testStateFile(t, originalState)
p := testProvider()
ui := new(cli.MockUi)
c := &PlanCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
testFixturePath("plan"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestPlan_validate(t *testing.T) { func TestPlan_validate(t *testing.T) {
// This is triggered by not asking for input so we have to set this to false // This is triggered by not asking for input so we have to set this to false
test = false test = false

View File

@ -13,7 +13,6 @@ import (
"strings" "strings"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform"
tfplugin "github.com/hashicorp/terraform/plugin" tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -280,9 +279,11 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory { func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory {
return map[string]terraform.ResourceProviderFactory{ return map[string]terraform.ResourceProviderFactory{
"terraform": func() (terraform.ResourceProvider, error) { // FIXME: Re-enable this once the internal provider system is updated
return terraformProvider.Provider(), nil // for the new provider interface.
}, //"terraform": func() (terraform.ResourceProvider, error) {
// return terraformProvider.Provider(), nil
//},
} }
} }

View File

@ -60,7 +60,7 @@ func (c *ProvidersCommand) Run(args []string) int {
// Get the state // Get the state
env := c.Workspace() env := c.Workspace()
state, err := b.State(env) state, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1

View File

@ -70,7 +70,7 @@ func (c *RefreshCommand) Run(args []string) int {
diags = nil diags = nil
// Build the operation // Build the operation
opReq := c.Operation() opReq := c.Operation(b)
opReq.Type = backend.OperationTypeRefresh opReq.Type = backend.OperationTypeRefresh
opReq.ConfigDir = configPath opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader() opReq.ConfigLoader, err = c.initConfigLoader()

View File

@ -2,18 +2,19 @@ package command
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/version"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestRefresh(t *testing.T) { func TestRefresh(t *testing.T) {
@ -185,263 +186,162 @@ func TestRefresh_cwd(t *testing.T) {
} }
func TestRefresh_defaultState(t *testing.T) { func TestRefresh_defaultState(t *testing.T) {
originalState := testState() t.Fatal("not yet updated for new provider types")
/*
originalState := testState()
// Write the state file in a temporary directory with the // Write the state file in a temporary directory with the
// default filename. // default filename.
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)
localState := &state.LocalState{Path: statePath} localState := &state.LocalState{Path: statePath}
if err := localState.RefreshState(); err != nil { if err := localState.RefreshState(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
s := localState.State() s := localState.State()
if s == nil { if s == nil {
t.Fatal("empty test state") t.Fatal("empty test state")
} }
serial := s.Serial serial := s.Serial
// Change to that directory // Change to that directory
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if err := os.Chdir(filepath.Dir(statePath)); err != nil { if err := os.Chdir(filepath.Dir(statePath)); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
defer os.Chdir(cwd) defer os.Chdir(cwd)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RefreshCommand{ c := &RefreshCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
}, },
} }
p.RefreshFn = nil p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes") p.RefreshReturn = newInstanceState("yes")
args := []string{ args := []string{
"-state", statePath, "-state", statePath,
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !p.RefreshCalled { if !p.RefreshCalled {
t.Fatal("refresh should be called") t.Fatal("refresh should be called")
} }
newState := testStateRead(t, statePath) newState := testStateRead(t, statePath)
actual := newState.RootModule().Resources["test_instance.foo"].Primary actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
expected := p.RefreshReturn expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Logf("expected:\n%#v", expected) t.Logf("expected:\n%#v", expected)
t.Fatalf("bad:\n%#v", actual) t.Fatalf("bad:\n%#v", actual)
} }
if newState.Serial <= serial { backupState := testStateRead(t, statePath+DefaultBackupExtension)
t.Fatalf("serial not incremented during refresh. previous:%d, current:%d", serial, newState.Serial)
}
backupState := testStateRead(t, statePath+DefaultBackupExtension) actual = backupState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
expected = originalState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
actual = backupState.RootModule().Resources["test_instance.foo"].Primary if !reflect.DeepEqual(actual, expected) {
expected = originalState.RootModule().Resources["test_instance.foo"].Primary t.Fatalf("bad: %#v", actual)
if !reflect.DeepEqual(actual, expected) { }
t.Fatalf("bad: %#v", actual) */
}
}
func TestRefresh_futureState(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(testFixturePath("refresh")); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
state := testState()
state.TFVersion = "99.99.99"
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
}
if code := c.Run(args); code == 0 {
t.Fatal("should fail")
}
if p.RefreshCalled {
t.Fatal("refresh should not be called")
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(newState.String())
expected := strings.TrimSpace(state.String())
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestRefresh_pastState(t *testing.T) {
state := testState()
state.TFVersion = "0.1.0"
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p.RefreshFn = nil
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
args := []string{
"-state", statePath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !p.RefreshCalled {
t.Fatal("refresh should be called")
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(newState.String())
expected := strings.TrimSpace(testRefreshStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
if newState.TFVersion != version.Version {
t.Fatalf("bad:\n\n%s", newState.TFVersion)
}
} }
func TestRefresh_outPath(t *testing.T) { func TestRefresh_outPath(t *testing.T) {
state := testState() t.Fatal("not yet updated for new provider types")
statePath := testStateFile(t, state) /*
state := testState()
statePath := testStateFile(t, state)
// Output path // Output path
outf, err := ioutil.TempFile(testingDir, "tf") outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
outPath := outf.Name() outPath := outf.Name()
outf.Close() outf.Close()
os.Remove(outPath) os.Remove(outPath)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RefreshCommand{ c := &RefreshCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
}, },
} }
p.RefreshFn = nil p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes") p.RefreshReturn = newInstanceState("yes")
args := []string{ args := []string{
"-state", statePath, "-state", statePath,
"-state-out", outPath, "-state-out", outPath,
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
} }
f, err := os.Open(statePath) f, err := os.Open(statePath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err := terraform.ReadState(f) newState, err := terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !reflect.DeepEqual(newState, state) { if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState) t.Fatalf("bad: %#v", newState)
} }
f, err = os.Open(outPath) f, err = os.Open(outPath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err = terraform.ReadState(f) newState, err = terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actual := newState.RootModule().Resources["test_instance.foo"].Primary actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)
} }
f, err = os.Open(outPath + DefaultBackupExtension) f, err = os.Open(outPath + DefaultBackupExtension)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
backupState, err := terraform.ReadState(f) backupState, err := terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actualStr := strings.TrimSpace(backupState.String()) actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String()) expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr { if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
} }
*/
} }
func TestRefresh_var(t *testing.T) { func TestRefresh_var(t *testing.T) {
@ -582,175 +482,181 @@ func TestRefresh_varsUnset(t *testing.T) {
} }
func TestRefresh_backup(t *testing.T) { func TestRefresh_backup(t *testing.T) {
state := testState() t.Fatal("not yet updated for new provider types")
statePath := testStateFile(t, state) /*
state := testState()
statePath := testStateFile(t, state)
// Output path // Output path
outf, err := ioutil.TempFile(testingDir, "tf") outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
outPath := outf.Name() outPath := outf.Name()
outf.Close() outf.Close()
os.Remove(outPath) os.Remove(outPath)
// Backup path // Backup path
backupf, err := ioutil.TempFile(testingDir, "tf") backupf, err := ioutil.TempFile(testingDir, "tf")
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
backupPath := backupf.Name() backupPath := backupf.Name()
backupf.Close() backupf.Close()
os.Remove(backupPath) os.Remove(backupPath)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RefreshCommand{ c := &RefreshCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
}, },
} }
p.RefreshFn = nil p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes") p.RefreshReturn = newInstanceState("yes")
args := []string{ args := []string{
"-state", statePath, "-state", statePath,
"-state-out", outPath, "-state-out", outPath,
"-backup", backupPath, "-backup", backupPath,
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
} }
f, err := os.Open(statePath) f, err := os.Open(statePath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err := terraform.ReadState(f) newState, err := terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !reflect.DeepEqual(newState, state) { if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState) t.Fatalf("bad: %#v", newState)
} }
f, err = os.Open(outPath) f, err = os.Open(outPath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err = terraform.ReadState(f) newState, err = terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actual := newState.RootModule().Resources["test_instance.foo"].Primary actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)
} }
f, err = os.Open(backupPath) f, err = os.Open(backupPath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
backupState, err := terraform.ReadState(f) backupState, err := terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actualStr := strings.TrimSpace(backupState.String()) actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String()) expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr { if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
} }
*/
} }
func TestRefresh_disableBackup(t *testing.T) { func TestRefresh_disableBackup(t *testing.T) {
state := testState() t.Fatal("not yet updated for new provider types")
statePath := testStateFile(t, state) /*
state := testState()
statePath := testStateFile(t, state)
// Output path // Output path
outf, err := ioutil.TempFile(testingDir, "tf") outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
outPath := outf.Name() outPath := outf.Name()
outf.Close() outf.Close()
os.Remove(outPath) os.Remove(outPath)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RefreshCommand{ c := &RefreshCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
}, },
} }
p.RefreshFn = nil p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes") p.RefreshReturn = newInstanceState("yes")
args := []string{ args := []string{
"-state", statePath, "-state", statePath,
"-state-out", outPath, "-state-out", outPath,
"-backup", "-", "-backup", "-",
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
} }
f, err := os.Open(statePath) f, err := os.Open(statePath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err := terraform.ReadState(f) newState, err := terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !reflect.DeepEqual(newState, state) { if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState) t.Fatalf("bad: %#v", newState)
} }
f, err = os.Open(outPath) f, err = os.Open(outPath)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
newState, err = terraform.ReadState(f) newState, err = terraform.ReadState(f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
actual := newState.RootModule().Resources["test_instance.foo"].Primary actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)
} }
// Ensure there is no backup // Ensure there is no backup
_, err = os.Stat(outPath + DefaultBackupExtension) _, err = os.Stat(outPath + DefaultBackupExtension)
if err == nil || !os.IsNotExist(err) { if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist") t.Fatalf("backup should not exist")
} }
_, err = os.Stat("-") _, err = os.Stat("-")
if err == nil || !os.IsNotExist(err) { if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist") t.Fatalf("backup should not exist")
} }
*/
} }
func TestRefresh_displaysOutputs(t *testing.T) { func TestRefresh_displaysOutputs(t *testing.T) {
@ -782,17 +688,21 @@ func TestRefresh_displaysOutputs(t *testing.T) {
} }
} }
// When creating an InstaneState for direct comparison to one contained in // newInstanceState creates a new states.ResourceInstanceObjectSrc with the
// terraform.State, all fields must be initialized (duplicating the // given value for its single id attribute. It is named newInstanceState for
// InstanceState.init() method) // historical reasons, because it was originally written for the poorly-named
func newInstanceState(id string) *terraform.InstanceState { // terraform.InstanceState type.
return &terraform.InstanceState{ func newInstanceState(id string) *states.ResourceInstanceObjectSrc {
ID: id, attrs := map[string]interface{}{
Attributes: make(map[string]string), "id": id,
Ephemeral: terraform.EphemeralState{ }
ConnInfo: make(map[string]string), attrsJSON, err := json.Marshal(attrs)
}, if err != nil {
Meta: make(map[string]interface{}), panic(fmt.Sprintf("failed to marshal attributes: %s", err)) // should never happen
}
return &states.ResourceInstanceObjectSrc{
AttrsJSON: attrsJSON,
Status: states.ObjectReady,
} }
} }

View File

@ -6,8 +6,12 @@ import (
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
) )
// ShowCommand is a Command implementation that reads and outputs the // ShowCommand is a Command implementation that reads and outputs the
@ -42,31 +46,30 @@ func (c *ShowCommand) Run(args []string) int {
var planErr, stateErr error var planErr, stateErr error
var path string var path string
var plan *terraform.Plan var plan *plans.Plan
var state *terraform.State var state *states.State
if len(args) > 0 { if len(args) > 0 {
path = args[0] path = args[0]
f, err := os.Open(path) pr, err := planfile.Open(path)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err)) f, err := os.Open(path)
return 1 if err != nil {
} c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
defer f.Close()
plan, err = terraform.ReadPlan(f)
if err != nil {
if _, err := f.Seek(0, 0); err != nil {
c.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
return 1 return 1
} }
defer f.Close()
plan = nil var stateFile *statefile.File
planErr = err stateFile, err = statefile.Read(f)
}
if plan == nil {
state, err = terraform.ReadState(f)
if err != nil { if err != nil {
stateErr = err stateErr = err
} else {
state = stateFile.State
}
} else {
plan, err = pr.ReadPlan()
if err != nil {
planErr = err
} }
} }
} else { } else {
@ -80,7 +83,7 @@ func (c *ShowCommand) Run(args []string) int {
env := c.Workspace() env := c.Workspace()
// Get the state // Get the state
stateStore, err := b.State(env) stateStore, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -110,7 +113,7 @@ func (c *ShowCommand) Run(args []string) int {
} }
if plan != nil { if plan != nil {
dispPlan := format.NewPlan(plan) dispPlan := format.NewPlan(plan.Changes)
c.Ui.Output(dispPlan.Format(c.Colorize())) c.Ui.Output(dispPlan.Format(c.Colorize()))
return 0 return 0
} }

View File

@ -2,12 +2,8 @@ package command
import ( import (
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -68,9 +64,7 @@ func TestShow_noArgsNoState(t *testing.T) {
} }
func TestShow_plan(t *testing.T) { func TestShow_plan(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{ planPath := testPlanFileNoop(t)
Config: configs.NewEmptyConfig(),
})
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &ShowCommand{ c := &ShowCommand{
@ -88,36 +82,6 @@ func TestShow_plan(t *testing.T) {
} }
} }
func TestShow_noArgsRemoteState(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
// Create some legacy remote state
legacyState := testState()
_, srv := testRemoteState(t, legacyState, 200)
defer srv.Close()
testStateFileRemote(t, legacyState)
ui := new(cli.MockUi)
c := &ShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui,
},
}
args := []string{}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
}
expected := "test_instance.foo"
actual := ui.OutputWriter.String()
if !strings.Contains(actual, expected) {
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
}
}
func TestShow_state(t *testing.T) { func TestShow_state(t *testing.T) {
originalState := testState() originalState := testState()
statePath := testStateFile(t, originalState) statePath := testStateFile(t, originalState)

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -23,7 +22,7 @@ func (c *StateListCommand) Run(args []string) int {
cmdFlags := c.Meta.flagSet("state list") cmdFlags := c.Meta.flagSet("state list")
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.") //lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.")
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
return cli.RunResultHelp return cli.RunResultHelp
} }
@ -38,7 +37,7 @@ func (c *StateListCommand) Run(args []string) int {
env := c.Workspace() env := c.Workspace()
// Get the state // Get the state
state, err := b.State(env) state, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -55,7 +54,11 @@ func (c *StateListCommand) Run(args []string) int {
return 1 return 1
} }
filter := &terraform.StateFilter{State: stateReal} // FIXME: update this for the new state types
c.Ui.Error("state list command not yet updated for new state types")
return 1
/*filter := &terraform.StateFilter{State: stateReal}
results, err := filter.Filter(args...) results, err := filter.Filter(args...)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err)) c.Ui.Error(fmt.Sprintf(errStateFilter, err))
@ -68,7 +71,7 @@ func (c *StateListCommand) Run(args []string) int {
c.Ui.Output(result.Address) c.Ui.Output(result.Address)
} }
} }
} }*/
return 0 return 0
} }

View File

@ -7,6 +7,7 @@ import (
backendLocal "github.com/hashicorp/terraform/backend/local" backendLocal "github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -26,9 +27,7 @@ func (c *StateMeta) State() (state.State, error) {
// use the specified state // use the specified state
if c.statePath != "" { if c.statePath != "" {
realState = &state.LocalState{ realState = statemgr.NewFilesystem(c.statePath)
Path: c.statePath,
}
} else { } else {
// Load the backend // Load the backend
b, backendDiags := c.Backend(nil) b, backendDiags := c.Backend(nil)
@ -36,9 +35,9 @@ func (c *StateMeta) State() (state.State, error) {
return nil, backendDiags.Err() return nil, backendDiags.Err()
} }
env := c.Workspace() workspace := c.Workspace()
// Get the state // Get the state
s, err := b.State(env) s, err := b.StateMgr(workspace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,8 +48,8 @@ func (c *StateMeta) State() (state.State, error) {
// This should never fail // This should never fail
panic(backendDiags.Err()) panic(backendDiags.Err())
} }
localB := localRaw.(*backendLocal.Local) localB := localRaw.(*backendlocal.Local)
_, stateOutPath, _ = localB.StatePaths(env) _, stateOutPath, _ = localB.StatePaths(workspace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,10 +69,10 @@ func (c *StateMeta) State() (state.State, error) {
DefaultBackupExtension) DefaultBackupExtension)
} }
// Wrap it for backups // If the backend is local (which it should always be, given our asserting
realState = &state.BackupState{ // of it above) we can now enable backups for it.
Real: realState, if lb, ok := realState.(*statemgr.Filesystem); ok {
Path: backupPath, lb.SetBackupPath(backupPath)
} }
return realState, nil return realState, nil

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -74,59 +75,62 @@ func (c *StateMvCommand) Run(args []string) int {
stateToReal = stateTo.State() stateToReal = stateTo.State()
if stateToReal == nil { if stateToReal == nil {
stateToReal = terraform.NewState() stateToReal = states.NewState()
} }
} }
// Filter what we're moving c.Ui.Error("state mv command not yet updated for new state types")
filter := &terraform.StateFilter{State: stateFromReal} /*
results, err := filter.Filter(args[0]) // Filter what we're moving
if err != nil { filter := &terraform.StateFilter{State: stateFromReal}
c.Ui.Error(fmt.Sprintf(errStateMv, err)) results, err := filter.Filter(args[0])
return cli.RunResultHelp if err != nil {
} c.Ui.Error(fmt.Sprintf(errStateMv, err))
if len(results) == 0 { return cli.RunResultHelp
c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0])) }
return 1 if len(results) == 0 {
} c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0]))
return 1
}
// Get the item to add to the state // Get the item to add to the state
add := c.addableResult(results) add := c.addableResult(results)
// Do the actual move // Do the actual move
if err := stateFromReal.Remove(args[0]); err != nil { if err := stateFromReal.Remove(args[0]); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err)) c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1 return 1
} }
if err := stateToReal.Add(args[0], args[1], add); err != nil { if err := stateToReal.Add(args[0], args[1], add); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err)) c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1 return 1
} }
// Write the new state // Write the new state
if err := stateTo.WriteState(stateToReal); err != nil { if err := stateTo.WriteState(stateToReal); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1
}
if err := stateTo.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1
}
// Write the old state if it is different
if stateTo != stateFrom {
if err := stateFrom.WriteState(stateFromReal); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1 return 1
} }
if err := stateFrom.PersistState(); err != nil { if err := stateTo.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1 return 1
} }
}
// Write the old state if it is different
if stateTo != stateFrom {
if err := stateFrom.WriteState(stateFromReal); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1
}
if err := stateFrom.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1
}
}
*/
c.Ui.Output(fmt.Sprintf( c.Ui.Output(fmt.Sprintf(
"Moved %s to %s", args[0], args[1])) "Moved %s to %s", args[0], args[1]))

View File

@ -1,47 +1,46 @@
package command package command
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestStateMv(t *testing.T) { func TestStateMv(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.baz": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -84,37 +83,32 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
backupPath := filepath.Join(td, "backup") backupPath := filepath.Join(td, "backup")
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.baz": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
// init our backend // init our backend
@ -162,37 +156,32 @@ func TestStateMv_backupExplicit(t *testing.T) {
defer os.RemoveAll(td) defer os.RemoveAll(td)
backupPath := filepath.Join(td, "backup") backupPath := filepath.Join(td, "backup")
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.baz": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -224,26 +213,20 @@ func TestStateMv_backupExplicit(t *testing.T) {
} }
func TestStateMv_stateOutNew(t *testing.T) { func TestStateMv_stateOutNew(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
stateOutPath := statePath + ".out" stateOutPath := statePath + ".out"
@ -281,44 +264,36 @@ func TestStateMv_stateOutNew(t *testing.T) {
} }
func TestStateMv_stateOutExisting(t *testing.T) { func TestStateMv_stateOutExisting(t *testing.T) {
stateSrc := &terraform.State{ stateSrc := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, stateSrc) statePath := testStateFile(t, stateSrc)
stateDst := &terraform.State{ stateDst := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.qux": &terraform.ResourceState{ Name: "qux",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
stateOutPath := testStateFile(t, stateDst) stateOutPath := testStateFile(t, stateDst)
p := testProvider() p := testProvider()
@ -382,48 +357,44 @@ func TestStateMv_noState(t *testing.T) {
} }
func TestStateMv_stateOutNew_count(t *testing.T) { func TestStateMv_stateOutNew_count(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo.0": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "foo", AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
stateOutPath := statePath + ".out" stateOutPath := statePath + ".out"
@ -463,147 +434,35 @@ func TestStateMv_stateOutNew_count(t *testing.T) {
// Modules with more than 10 resources were sorted lexically, causing the // Modules with more than 10 resources were sorted lexically, causing the
// indexes in the new location to change. // indexes in the new location to change.
func TestStateMv_stateOutNew_largeCount(t *testing.T) { func TestStateMv_stateOutNew_largeCount(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ // test_instance.foo has 11 instances, all the same except for their ids
&terraform.ModuleState{ for i := 0; i < 11; i++ {
Path: []string{"root"}, s.SetResourceInstanceCurrent(
Resources: map[string]*terraform.ResourceState{ addrs.Resource{
"test_instance.foo.0": &terraform.ResourceState{ Mode: addrs.ManagedResourceMode,
Type: "test_instance", Type: "test_instance",
Primary: &terraform.InstanceState{ Name: "foo",
ID: "foo0", }.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance),
Attributes: map[string]string{ &states.ResourceInstanceObjectSrc{
"foo": "value", AttrsJSON: []byte(fmt.Sprintf(`{"id":"foo%d","foo":"value","bar":"value"}`, i)),
"bar": "value", Status: states.ObjectReady,
},
},
},
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo1",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.2": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo2",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.3": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo3",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.4": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo4",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.5": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo5",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.6": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo6",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.7": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo7",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.8": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo8",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.9": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo9",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.10": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo10",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
}, },
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
}
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
stateOutPath := statePath + ".out" stateOutPath := statePath + ".out"
@ -641,51 +500,32 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) {
} }
func TestStateMv_stateOutNew_nestedModule(t *testing.T) { func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{}, Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("child1", addrs.NoKey)),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
}, },
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
&terraform.ModuleState{ )
Path: []string{"root", "foo"}, s.SetResourceInstanceCurrent(
Resources: map[string]*terraform.ResourceState{}, addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("child2", addrs.NoKey)),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
}, },
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
&terraform.ModuleState{ )
Path: []string{"root", "foo", "child1"}, })
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
},
&terraform.ModuleState{
Path: []string{"root", "foo", "child2"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
},
},
}
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
stateOutPath := statePath + ".out" stateOutPath := statePath + ".out"

View File

@ -1,11 +1,9 @@
package command package command
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -36,7 +34,7 @@ func (c *StatePullCommand) Run(args []string) int {
// Get the state // Get the state
env := c.Workspace() env := c.Workspace()
state, err := b.State(env) state, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -54,13 +52,19 @@ func (c *StatePullCommand) Run(args []string) int {
return 0 return 0
} }
var buf bytes.Buffer c.Ui.Error("state pull not yet updated for new state types")
if err := terraform.WriteState(s, &buf); err != nil { return 1
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 /*
} var buf bytes.Buffer
if err := terraform.WriteState(s, &buf); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
c.Ui.Output(buf.String())
*/
c.Ui.Output(buf.String())
return 0 return 0
} }

View File

@ -13,9 +13,9 @@ func TestStatePull(t *testing.T) {
// Create some legacy remote state // Create some legacy remote state
legacyState := testState() legacyState := testState()
_, srv := testRemoteState(t, legacyState, 200) backendState, srv := testRemoteState(t, legacyState, 200)
defer srv.Close() defer srv.Close()
testStateFileRemote(t, legacyState) testStateFileRemote(t, backendState)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)

View File

@ -1,12 +1,8 @@
package command package command
import ( import (
"fmt"
"io"
"os"
"strings" "strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -35,80 +31,86 @@ func (c *StatePushCommand) Run(args []string) int {
return 1 return 1
} }
// Determine our reader for the input state. This is the filepath c.Ui.Error("state push not yet updated for new state types")
// or stdin if "-" is given. return 1
var r io.Reader = os.Stdin
if args[0] != "-" { /*
f, err := os.Open(args[0]) // Determine our reader for the input state. This is the filepath
// or stdin if "-" is given.
var r io.Reader = os.Stdin
if args[0] != "-" {
f, err := os.Open(args[0])
if err != nil {
c.Ui.Error(err.Error())
return 1
}
// Note: we don't need to defer a Close here because we do a close
// automatically below directly after the read.
r = f
}
// Read the state
sourceState, err := terraform.ReadState(r)
if c, ok := r.(io.Closer); ok {
// Close the reader if possible right now since we're done with it.
c.Close()
}
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
return 1 return 1
} }
// Note: we don't need to defer a Close here because we do a close // Load the backend
// automatically below directly after the read. b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
r = f c.showDiagnostics(backendDiags)
}
// Read the state
sourceState, err := terraform.ReadState(r)
if c, ok := r.(io.Closer); ok {
// Close the reader if possible right now since we're done with it.
c.Close()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}
// Get the state
env := c.Workspace()
state, err := b.State(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
if err := state.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
dstState := state.State()
// If we're not forcing, then perform safety checks
if !flagForce && !dstState.Empty() {
if !dstState.SameLineage(sourceState) {
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
return 1 return 1
} }
age, err := dstState.CompareAges(sourceState) // Get the state
env := c.Workspace()
state, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1 return 1
} }
if age == terraform.StateAgeReceiverNewer { if err := state.RefreshState(); err != nil {
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer)) c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1 return 1
} }
}
// Overwrite it dstState := state.State()
if err := state.WriteState(sourceState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) // If we're not forcing, then perform safety checks
return 1 if !flagForce && !dstState.Empty() {
} if !dstState.SameLineage(sourceState) {
if err := state.PersistState(); err != nil { c.Ui.Error(strings.TrimSpace(errStatePushLineage))
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) return 1
return 1 }
}
age, err := dstState.CompareAges(sourceState)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if age == terraform.StateAgeReceiverNewer {
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
return 1
}
}
// Overwrite it
if err := state.WriteState(sourceState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
if err := state.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
*/
return 0 return 0
} }

View File

@ -8,7 +8,7 @@ import (
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/backend/remote-state/inmem" "github.com/hashicorp/terraform/backend/remote-state/inmem"
"github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -81,7 +81,7 @@ func TestStatePush_replaceMatchStdin(t *testing.T) {
// Setup the replacement to come from stdin // Setup the replacement to come from stdin
var buf bytes.Buffer var buf bytes.Buffer
if err := terraform.WriteState(expected, &buf); err != nil { if err := writeStateForTesting(expected, &buf); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
defer testStdinPipe(t, &buf)() defer testStdinPipe(t, &buf)()
@ -200,7 +200,7 @@ func TestStatePush_forceRemoteState(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
defer inmem.Reset() defer inmem.Reset()
s := terraform.NewState() s := states.NewState()
statePath := testStateFile(t, s) statePath := testStateFile(t, s)
// init the backend // init the backend
@ -223,11 +223,11 @@ func TestStatePush_forceRemoteState(t *testing.T) {
// put a dummy state in place, so we have something to force // put a dummy state in place, so we have something to force
b := backend.TestBackendConfig(t, inmem.New(), nil) b := backend.TestBackendConfig(t, inmem.New(), nil)
sMgr, err := b.State("test") sMgr, err := b.StateMgr("test")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := sMgr.WriteState(terraform.NewState()); err != nil { if err := sMgr.WriteState(states.NewState()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := sMgr.PersistState(); err != nil { if err := sMgr.PersistState(); err != nil {

View File

@ -47,10 +47,15 @@ func (c *StateRmCommand) Run(args []string) int {
return 1 return 1
} }
if err := stateReal.Remove(args...); err != nil { c.Ui.Error("state rm not yet updated for new state types")
c.Ui.Error(fmt.Sprintf(errStateRm, err)) return 1
return 1
} /*
if err := stateReal.Remove(args...); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRm, err))
return 1
}
*/
c.Ui.Output(fmt.Sprintf("%d items removed.", len(args))) c.Ui.Output(fmt.Sprintf("%d items removed.", len(args)))

View File

@ -6,43 +6,41 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestStateRm(t *testing.T) { func TestStateRm(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -76,37 +74,32 @@ func TestStateRm(t *testing.T) {
} }
func TestStateRmNoArgs(t *testing.T) { func TestStateRmNoArgs(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -138,37 +131,32 @@ func TestStateRm_backupExplicit(t *testing.T) {
defer os.RemoveAll(td) defer os.RemoveAll(td)
backupPath := filepath.Join(td, "backup") backupPath := filepath.Join(td, "backup")
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.bar": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()

View File

@ -2,12 +2,9 @@ package command
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/ryanuber/columnize"
) )
// StateShowCommand is a Command implementation that shows a single resource. // StateShowCommand is a Command implementation that shows a single resource.
@ -38,7 +35,7 @@ func (c *StateShowCommand) Run(args []string) int {
// Get the state // Get the state
env := c.Workspace() env := c.Workspace()
state, err := b.State(env) state, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -54,50 +51,55 @@ func (c *StateShowCommand) Run(args []string) int {
return 1 return 1
} }
filter := &terraform.StateFilter{State: stateReal} c.Ui.Error("state show not yet updated for new state types")
results, err := filter.Filter(args...) return 1
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
return 1
}
if len(results) == 0 { /*
return 0 filter := &terraform.StateFilter{State: stateReal}
} results, err := filter.Filter(args...)
if err != nil {
instance, err := c.filterInstance(results) c.Ui.Error(fmt.Sprintf(errStateFilter, err))
if err != nil { return 1
c.Ui.Error(err.Error())
return 1
}
if instance == nil {
return 0
}
is := instance.Value.(*terraform.InstanceState)
// Sort the keys
var keys []string
for k, _ := range is.Attributes {
keys = append(keys, k)
}
sort.Strings(keys)
// Build the output
var output []string
output = append(output, fmt.Sprintf("id | %s", is.ID))
for _, k := range keys {
if k != "id" {
output = append(output, fmt.Sprintf("%s | %s", k, is.Attributes[k]))
} }
}
// Output if len(results) == 0 {
config := columnize.DefaultConfig() return 0
config.Glue = " = " }
c.Ui.Output(columnize.Format(output, config))
return 0 instance, err := c.filterInstance(results)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if instance == nil {
return 0
}
is := instance.Value.(*terraform.InstanceState)
// Sort the keys
var keys []string
for k, _ := range is.Attributes {
keys = append(keys, k)
}
sort.Strings(keys)
// Build the output
var output []string
output = append(output, fmt.Sprintf("id | %s", is.ID))
for _, k := range keys {
if k != "id" {
output = append(output, fmt.Sprintf("%s | %s", k, is.Attributes[k]))
}
}
// Output
config := columnize.DefaultConfig()
config.Glue = " = "
c.Ui.Output(columnize.Format(output, config))
return 0
*/
} }
func (c *StateShowCommand) Help() string { func (c *StateShowCommand) Help() string {

View File

@ -4,31 +4,27 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
) )
func TestStateShow(t *testing.T) { func TestStateShow(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -57,36 +53,32 @@ func TestStateShow(t *testing.T) {
} }
func TestStateShow_multi(t *testing.T) { func TestStateShow_multi(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo.0": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Attributes: map[string]string{ Status: states.ObjectReady,
"foo": "value",
"bar": "value",
},
},
},
"test_instance.foo.1": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -127,8 +119,7 @@ func TestStateShow_noState(t *testing.T) {
} }
func TestStateShow_emptyState(t *testing.T) { func TestStateShow_emptyState(t *testing.T) {
state := terraform.NewState() state := states.NewState()
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
p := testProvider() p := testProvider()
@ -149,34 +140,6 @@ func TestStateShow_emptyState(t *testing.T) {
} }
} }
func TestStateShow_emptyStateWithModule(t *testing.T) {
// empty state with empty module
state := terraform.NewState()
mod := &terraform.ModuleState{
Path: []string{"root", "mod"},
}
state.Modules = append(state.Modules, mod)
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
const testStateShowOutput = ` const testStateShowOutput = `
id = bar id = bar
bar = value bar = value

View File

@ -3,12 +3,13 @@ package command
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"strings" "strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/tfdiags"
) )
// TaintCommand is a cli.Command implementation that manually taints // TaintCommand is a cli.Command implementation that manually taints
@ -38,6 +39,8 @@ func (c *TaintCommand) Run(args []string) int {
return 1 return 1
} }
var diags tfdiags.Diagnostics
// Require the one argument for the resource to taint // Require the one argument for the resource to taint
args = cmdFlags.Args() args = cmdFlags.Args()
if len(args) != 1 { if len(args) != 1 {
@ -46,34 +49,34 @@ func (c *TaintCommand) Run(args []string) int {
return 1 return 1
} }
name := args[0] if module != "" {
if module == "" { c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".")
module = "root"
} else {
module = "root." + module
}
rsk, err := terraform.ParseResourceStateKey(name)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err))
return 1 return 1
} }
if !rsk.Mode.Taintable() { addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name)) diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr))
return 1 return 1
} }
// Load the backend // Load the backend
b, backendDiags := c.Backend(nil) b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() { if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags) c.showDiagnostics(diags)
return 1 return 1
} }
// Get the state // Get the state
env := c.Workspace() env := c.Workspace()
st, err := b.State(env) st, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -97,63 +100,60 @@ func (c *TaintCommand) Run(args []string) int {
s := st.State() s := st.State()
if s.Empty() { if s.Empty() {
if allowMissing { if allowMissing {
return c.allowMissingExit(name, module) return c.allowMissingExit(addr)
} }
c.Ui.Error(fmt.Sprintf( diags = diags.Append(tfdiags.Sourceless(
"The state is empty. The most common reason for this is that\n" + tfdiags.Error,
"an invalid state file path was given or Terraform has never\n " + "No such resource instance",
"been run for this infrastructure. Infrastructure must exist\n" + "The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
"for it to be tainted.")) ))
c.showDiagnostics(diags)
return 1 return 1
} }
// Get the ModuleState where we will taint. This is provided in a legacy state := s.SyncWrapper()
// string form that doesn't support module instance keys, so we'll shim
// it here. // Get the resource and instance we're going to taint
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim() rs := state.Resource(addr.ContainingResource())
mod := s.ModuleByPath(modPath) is := state.ResourceInstance(addr)
if mod == nil { if is == nil {
if allowMissing { if allowMissing {
return c.allowMissingExit(name, module) return c.allowMissingExit(addr)
} }
c.Ui.Error(fmt.Sprintf( diags = diags.Append(tfdiags.Sourceless(
"The module %s could not be found. There is nothing to taint.", tfdiags.Error,
module)) "No such resource instance",
fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
))
c.showDiagnostics(diags)
return 1 return 1
} }
// If there are no resources in this module, it is an error obj := is.Current
if len(mod.Resources) == 0 { if obj == nil {
if allowMissing { if len(is.Deposed) != 0 {
return c.allowMissingExit(name, module) diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
))
} else {
// Don't know why we're here, but we'll produce a generic error message anyway.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
))
} }
c.showDiagnostics(diags)
c.Ui.Error(fmt.Sprintf(
"The module %s has no resources. There is nothing to taint.",
module))
return 1 return 1
} }
// Get the resource we're looking for obj.Status = states.ObjectTainted
rs, ok := mod.Resources[name] state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
if !ok {
if allowMissing {
return c.allowMissingExit(name, module)
}
c.Ui.Error(fmt.Sprintf(
"The resource %s couldn't be found in the module %s.",
name,
module))
return 1
}
// Taint the resource
rs.Taint()
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
if err := st.WriteState(s); err != nil { if err := st.WriteState(s); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1 return 1
@ -163,24 +163,28 @@ func (c *TaintCommand) Run(args []string) int {
return 1 return 1
} }
c.Ui.Output(fmt.Sprintf( c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr))
"The resource %s in the module %s has been marked as tainted!",
name, module))
return 0 return 0
} }
func (c *TaintCommand) Help() string { func (c *TaintCommand) Help() string {
helpText := ` helpText := `
Usage: terraform taint [options] name Usage: terraform taint [options] <address>
Manually mark a resource as tainted, forcing a destroy and recreate Manually mark a resource as tainted, forcing a destroy and recreate
on the next plan/apply. on the next plan/apply.
This will not modify your infrastructure. This command changes your This will not modify your infrastructure. This command changes your
state to mark a resource as tainted so that during the next plan or state to mark a resource as tainted so that during the next plan or
apply, that resource will be destroyed and recreated. This command on apply that resource will be destroyed and recreated. This command on
its own will not modify infrastructure. This command can be undone by its own will not modify infrastructure. This command can be undone
reverting the state backup file that is created. using the "terraform untaint" command with the same address.
The address is in the usual resource address syntax, as shown in
the output from other commands, such as:
aws_instance.foo
aws_instance.bar[1]
module.foo.module.bar.aws_instance.baz
Options: Options:
@ -195,10 +199,6 @@ Options:
-lock-timeout=0s Duration to retry a state lock. -lock-timeout=0s Duration to retry a state lock.
-module=path The module path where the resource lives. By
default this will be root. Child modules can be specified
by names. Ex. "consul" or "consul.vpc" (nested modules).
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-state=path Path to read and save state (unless state-out -state=path Path to read and save state (unless state-out
@ -215,10 +215,11 @@ func (c *TaintCommand) Synopsis() string {
return "Manually mark a resource for recreation" return "Manually mark a resource for recreation"
} }
func (c *TaintCommand) allowMissingExit(name, module string) int { func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
c.Ui.Output(fmt.Sprintf( c.showDiagnostics(tfdiags.Sourceless(
"The resource %s in the module %s was not found, but\n"+ tfdiags.Warning,
"-allow-missing is set, so we're exiting successfully.", "No such resource instance",
name, module)) "Resource instance %s was not found, but this is not an error because -allow-missing was set.",
))
return 0 return 0
} }

View File

@ -5,26 +5,28 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
) )
func TestTaint(t *testing.T) { func TestTaint(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -46,21 +48,20 @@ func TestTaint(t *testing.T) {
} }
func TestTaint_lockedState(t *testing.T) { func TestTaint_lockedState(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
unlock, err := testLockState("./testdata", statePath) unlock, err := testLockState("./testdata", statePath)
@ -233,21 +234,20 @@ func TestTaint_defaultState(t *testing.T) {
} }
func TestTaint_missing(t *testing.T) { func TestTaint_missing(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -267,21 +267,20 @@ func TestTaint_missing(t *testing.T) {
} }
func TestTaint_missingAllow(t *testing.T) { func TestTaint_missingAllow(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -344,32 +343,32 @@ func TestTaint_stateOut(t *testing.T) {
} }
func TestTaint_module(t *testing.T) { func TestTaint_module(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectReady,
},
},
}, },
&terraform.ModuleState{ addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root", "child"}, )
Resources: map[string]*terraform.ResourceState{ s.SetResourceInstanceCurrent(
"test_instance.blah": &terraform.ResourceState{ addrs.Resource{
Type: "test_instance", Mode: addrs.ManagedResourceMode,
Primary: &terraform.InstanceState{ Type: "test_instance",
ID: "blah", Name: "blah",
}, }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
}, &states.ResourceInstanceObjectSrc{
}, AttrsJSON: []byte(`{"id":"blah"}`),
Status: states.ObjectReady,
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)

View File

@ -4,7 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -67,21 +68,13 @@ func (c *UnlockCommand) Run(args []string) int {
} }
env := c.Workspace() env := c.Workspace()
st, err := b.State(env) st, err := b.StateMgr(env)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
} }
isLocal := false _, isLocal := st.(*statemgr.Filesystem)
switch s := st.(type) {
case *state.BackupState:
if _, ok := s.Real.(*state.LocalState); ok {
isLocal = true
}
case *state.LocalState:
isLocal = true
}
if !force { if !force {
// Forcing this doesn't do anything, but doesn't break anything either, // Forcing this doesn't do anything, but doesn't break anything either,

View File

@ -25,7 +25,7 @@ func TestUnlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
err = terraform.WriteState(testState(), f) err = terraform.WriteState(terraform.NewState(), f)
f.Close() f.Close()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)

View File

@ -3,9 +3,12 @@ package command
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"strings" "strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
) )
@ -37,6 +40,8 @@ func (c *UntaintCommand) Run(args []string) int {
return 1 return 1
} }
var diags tfdiags.Diagnostics
// Require the one argument for the resource to untaint // Require the one argument for the resource to untaint
args = cmdFlags.Args() args = cmdFlags.Args()
if len(args) != 1 { if len(args) != 1 {
@ -45,23 +50,29 @@ func (c *UntaintCommand) Run(args []string) int {
return 1 return 1
} }
name := args[0] if module != "" {
if module == "" { c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".")
module = "root" return 1
} else { }
module = "root." + module
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
} }
// Load the backend // Load the backend
b, backendDiags := c.Backend(nil) b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() { if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags) c.showDiagnostics(diags)
return 1 return 1
} }
// Get the state // Get the state
env := c.Workspace() workspace := c.Workspace()
st, err := b.State(env) st, err := b.StateMgr(workspace)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
@ -85,63 +96,69 @@ func (c *UntaintCommand) Run(args []string) int {
s := st.State() s := st.State()
if s.Empty() { if s.Empty() {
if allowMissing { if allowMissing {
return c.allowMissingExit(name, module) return c.allowMissingExit(addr)
} }
c.Ui.Error(fmt.Sprintf( diags = diags.Append(tfdiags.Sourceless(
"The state is empty. The most common reason for this is that\n" + tfdiags.Error,
"an invalid state file path was given or Terraform has never\n " + "No such resource instance",
"been run for this infrastructure. Infrastructure must exist\n" + "The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
"for it to be untainted.")) ))
c.showDiagnostics(diags)
return 1 return 1
} }
// Get the ModuleState where we will untaint. This is provided in a legacy state := s.SyncWrapper()
// string form that doesn't support module instance keys, so we'll shim
// it here. // Get the resource and instance we're going to taint
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim() rs := state.Resource(addr.ContainingResource())
mod := s.ModuleByPath(modPath) is := state.ResourceInstance(addr)
if mod == nil { if is == nil {
if allowMissing { if allowMissing {
return c.allowMissingExit(name, module) return c.allowMissingExit(addr)
} }
c.Ui.Error(fmt.Sprintf( diags = diags.Append(tfdiags.Sourceless(
"The module %s could not be found. There is nothing to untaint.", tfdiags.Error,
module)) "No such resource instance",
fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
))
c.showDiagnostics(diags)
return 1 return 1
} }
// If there are no resources in this module, it is an error obj := is.Current
if len(mod.Resources) == 0 { if obj == nil {
if allowMissing { if len(is.Deposed) != 0 {
return c.allowMissingExit(name, module) diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
))
} else {
// Don't know why we're here, but we'll produce a generic error message anyway.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
))
} }
c.showDiagnostics(diags)
c.Ui.Error(fmt.Sprintf(
"The module %s has no resources. There is nothing to untaint.",
module))
return 1 return 1
} }
// Get the resource we're looking for if obj.Status != states.ObjectTainted {
rs, ok := mod.Resources[name] diags = diags.Append(tfdiags.Sourceless(
if !ok { tfdiags.Error,
if allowMissing { "Resource instance is not tainted",
return c.allowMissingExit(name, module) fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr),
} ))
c.showDiagnostics(diags)
c.Ui.Error(fmt.Sprintf(
"The resource %s couldn't be found in the module %s.",
name,
module))
return 1 return 1
} }
obj.Status = states.ObjectReady
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
// Untaint the resource
rs.Untaint()
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
if err := st.WriteState(s); err != nil { if err := st.WriteState(s); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1 return 1
@ -151,9 +168,7 @@ func (c *UntaintCommand) Run(args []string) int {
return 1 return 1
} }
c.Ui.Output(fmt.Sprintf( c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr))
"The resource %s in the module %s has been successfully untainted!",
name, module))
return 0 return 0
} }
@ -203,10 +218,11 @@ func (c *UntaintCommand) Synopsis() string {
return "Manually unmark a resource as tainted" return "Manually unmark a resource as tainted"
} }
func (c *UntaintCommand) allowMissingExit(name, module string) int { func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
c.Ui.Output(fmt.Sprintf( c.showDiagnostics(tfdiags.Sourceless(
"The resource %s in the module %s was not found, but\n"+ tfdiags.Warning,
"-allow-missing is set, so we're exiting successfully.", "No such resource instance",
name, module)) "Resource instance %s was not found, but this is not an error because -allow-missing was set.",
))
return 0 return 0
} }

View File

@ -5,27 +5,27 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestUntaint(t *testing.T) { func TestUntaint(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
Tainted: true, Status: states.ObjectTainted,
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -51,22 +51,20 @@ test_instance.foo:
} }
func TestUntaint_lockedState(t *testing.T) { func TestUntaint_lockedState(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
Tainted: true, Status: states.ObjectTainted,
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
unlock, err := testLockState("./testdata", statePath) unlock, err := testLockState("./testdata", statePath)
if err != nil { if err != nil {
@ -257,22 +255,20 @@ test_instance.foo:
} }
func TestUntaint_missing(t *testing.T) { func TestUntaint_missing(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
Tainted: true, Status: states.ObjectTainted,
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -292,22 +288,20 @@ func TestUntaint_missing(t *testing.T) {
} }
func TestUntaint_missingAllow(t *testing.T) { func TestUntaint_missingAllow(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
Tainted: true, Status: states.ObjectTainted,
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -377,34 +371,32 @@ test_instance.foo:
} }
func TestUntaint_module(t *testing.T) { func TestUntaint_module(t *testing.T) {
state := &terraform.State{ state := states.BuildState(func(s *states.SyncState) {
Modules: []*terraform.ModuleState{ s.SetResourceInstanceCurrent(
&terraform.ModuleState{ addrs.Resource{
Path: []string{"root"}, Mode: addrs.ManagedResourceMode,
Resources: map[string]*terraform.ResourceState{ Type: "test_instance",
"test_instance.foo": &terraform.ResourceState{ Name: "foo",
Type: "test_instance", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: "bar", AttrsJSON: []byte(`{"id":"bar"}`),
Tainted: true, Status: states.ObjectTainted,
},
},
},
}, },
&terraform.ModuleState{ addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
Path: []string{"root", "child"}, )
Resources: map[string]*terraform.ResourceState{ s.SetResourceInstanceCurrent(
"test_instance.blah": &terraform.ResourceState{ addrs.Resource{
Type: "test_instance", Mode: addrs.ManagedResourceMode,
Primary: &terraform.InstanceState{ Type: "test_instance",
ID: "bar", Name: "blah",
Tainted: true, }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
}, &states.ResourceInstanceObjectSrc{
}, AttrsJSON: []byte(`{"id":"bar"}`),
}, Status: states.ObjectTainted,
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)

View File

@ -7,11 +7,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/backend/local" "github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/backend/remote-state/inmem" "github.com/hashicorp/terraform/backend/remote-state/inmem"
"github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -227,24 +230,22 @@ func TestWorkspace_createWithState(t *testing.T) {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
} }
// create a non-empty state originalState := states.BuildState(func(s *states.SyncState) {
originalState := &terraform.State{ s.SetResourceInstanceCurrent(
Modules: []*terraform.ModuleState{ addrs.Resource{
&terraform.ModuleState{ Mode: addrs.ManagedResourceMode,
Path: []string{"root"}, Type: "test_instance",
Resources: map[string]*terraform.ResourceState{ Name: "foo",
"test_instance.foo": &terraform.ResourceState{ }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
Type: "test_instance", &states.ResourceInstanceObjectSrc{
Primary: &terraform.InstanceState{ AttrsJSON: []byte(`{"id":"bar"}`),
ID: "bar", Status: states.ObjectReady,
},
},
},
}, },
}, addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
} )
})
err := (&state.LocalState{Path: "test.tfstate"}).WriteState(originalState) err := statemgr.NewFilesystem("test.tfstate").WriteState(originalState)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -268,14 +269,13 @@ func TestWorkspace_createWithState(t *testing.T) {
} }
b := backend.TestBackendConfig(t, inmem.New(), nil) b := backend.TestBackendConfig(t, inmem.New(), nil)
sMgr, err := b.State(workspace) sMgr, err := b.StateMgr(workspace)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
newState := sMgr.State() newState := sMgr.State()
originalState.Version = newState.Version // the round-trip through the state manager implicitly populates version
if !originalState.Equal(newState) { if !originalState.Equal(newState) {
t.Fatalf("states not equal\norig: %s\nnew: %s", originalState, newState) t.Fatalf("states not equal\norig: %s\nnew: %s", originalState, newState)
} }

View File

@ -69,7 +69,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
return 1 return 1
} }
states, err := b.States() states, err := b.Workspaces()
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
@ -94,7 +94,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
} }
// we need the actual state to see if it's empty // we need the actual state to see if it's empty
sMgr, err := b.State(delEnv) sMgr, err := b.StateMgr(delEnv)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
@ -134,7 +134,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
// be delegated from the Backend to the State itself. // be delegated from the Backend to the State itself.
stateLocker.Unlock(nil) stateLocker.Unlock(nil)
err = b.DeleteState(delEnv) err = b.DeleteWorkspace(delEnv)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1

View File

@ -53,7 +53,7 @@ func (c *WorkspaceListCommand) Run(args []string) int {
return 1 return 1
} }
states, err := b.States() states, err := b.Workspaces()
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/posener/complete" "github.com/posener/complete"
@ -79,7 +79,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
return 1 return 1
} }
states, err := b.States() states, err := b.Workspaces()
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err))
return 1 return 1
@ -91,7 +91,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
} }
} }
_, err = b.State(newEnv) _, err = b.StateMgr(newEnv)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
@ -112,7 +112,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
} }
// load the new Backend state // load the new Backend state
sMgr, err := b.State(newEnv) sMgr, err := b.StateMgr(newEnv)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
@ -128,20 +128,20 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
} }
// read the existing state file // read the existing state file
stateFile, err := os.Open(statePath) f, err := os.Open(statePath)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
s, err := terraform.ReadState(stateFile) stateFile, err := statefile.Read(f)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
// save the existing state in the new Backend. // save the existing state in the new Backend.
err = sMgr.WriteState(s) err = sMgr.WriteState(stateFile.State)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1

View File

@ -75,7 +75,7 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
return 1 return 1
} }
states, err := b.States() states, err := b.Workspaces()
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1

View File

@ -2,6 +2,7 @@ package configload
import ( import (
"fmt" "fmt"
"path/filepath"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/registry"
@ -91,3 +92,35 @@ func (l *Loader) Sources() map[string][]byte {
func (l *Loader) IsConfigDir(path string) bool { func (l *Loader) IsConfigDir(path string) bool {
return l.parser.IsConfigDir(path) return l.parser.IsConfigDir(path)
} }
// ImportSources writes into the receiver's source code the given source
// code buffers.
//
// This is useful in the situation where an ancillary loader is created for
// some reason (e.g. loading config from a plan file) but the cached source
// code from that loader must be imported into the "main" loader in order
// to return source code snapshots in diagnostic messages.
//
// loader.ImportSources(otherLoader.Sources())
func (l *Loader) ImportSources(sources map[string][]byte) {
p := l.Parser()
for name, src := range sources {
p.ForceFileSource(name, src)
}
}
// ImportSourcesFromSnapshot writes into the receiver's source code the
// source files from the given snapshot.
//
// This is similar to ImportSources but knows how to unpack and flatten a
// snapshot data structure to get the corresponding flat source file map.
func (l *Loader) ImportSourcesFromSnapshot(snap *Snapshot) {
p := l.Parser()
for _, m := range snap.Modules {
baseDir := m.Dir
for fn, src := range m.Files {
fullPath := filepath.Join(baseDir, fn)
p.ForceFileSource(fullPath, src)
}
}
}

View File

@ -14,16 +14,16 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/logutils" "github.com/hashicorp/logutils"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -674,17 +674,24 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
return nil return nil
} }
name := fmt.Sprintf("%s.foo", r.Type) addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: r.Type,
Name: "foo",
}.Instance(addrs.NoKey)
absAddr := addr.Absolute(addrs.RootModuleInstance)
// Build the state. The state is just the resource with an ID. There // Build the state. The state is just the resource with an ID. There
// are no attributes. We only set what is needed to perform a refresh. // are no attributes. We only set what is needed to perform a refresh.
state := terraform.NewState() state := states.NewState()
state.RootModule().Resources[name] = &terraform.ResourceState{ state.RootModule().SetResourceInstanceCurrent(
Type: r.Type, addr,
Primary: &terraform.InstanceState{ &states.ResourceInstanceObjectSrc{
ID: r.Primary.ID, AttrsFlat: r.Primary.Attributes,
Status: states.ObjectReady,
}, },
} addrs.ProviderConfig{Type: "placeholder"}.Absolute(addrs.RootModuleInstance),
)
// Create the config module. We use the full config because Refresh // Create the config module. We use the full config because Refresh
// doesn't have access to it and we may need things like provider // doesn't have access to it and we may need things like provider
@ -717,14 +724,14 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
} }
// Verify attribute equivalence. // Verify attribute equivalence.
actualR := state.RootModule().Resources[name] actualR := state.ResourceInstance(absAddr)
if actualR == nil { if actualR == nil {
return fmt.Errorf("Resource gone!") return fmt.Errorf("Resource gone!")
} }
if actualR.Primary == nil { if actualR.Current == nil {
return fmt.Errorf("Resource has no primary instance") return fmt.Errorf("Resource has no primary instance")
} }
actual := actualR.Primary.Attributes actual := actualR.Current.AttrsFlat
expected := r.Primary.Attributes expected := r.Primary.Attributes
// Remove fields we're ignoring // Remove fields we're ignoring
for _, v := range c.IDRefreshIgnore { for _, v := range c.IDRefreshIgnore {

View File

@ -3,12 +3,7 @@ package resource
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"strings"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -20,149 +15,149 @@ func testStepConfig(
return testStep(opts, state, step) return testStep(opts, state, step)
} }
func testStep( func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) {
opts terraform.ContextOpts, return nil, fmt.Errorf("testStep not yet updated for new state type")
state *terraform.State, /*
step TestStep) (*terraform.State, error) { // Pre-taint any resources that have been defined in Taint, as long as this
// Pre-taint any resources that have been defined in Taint, as long as this // is not a destroy step.
// is not a destroy step. if !step.Destroy {
if !step.Destroy { if err := testStepTaint(state, step); err != nil {
if err := testStepTaint(state, step); err != nil { return state, err
}
}
cfg, err := testConfig(opts, step)
if err != nil {
return state, err return state, err
} }
}
cfg, err := testConfig(opts, step) var stepDiags tfdiags.Diagnostics
if err != nil {
return state, err
}
var stepDiags tfdiags.Diagnostics // Build the context
opts.Config = cfg
// Build the context opts.State = state
opts.Config = cfg opts.Destroy = step.Destroy
opts.State = state ctx, stepDiags := terraform.NewContext(&opts)
opts.Destroy = step.Destroy
ctx, stepDiags := terraform.NewContext(&opts)
if stepDiags.HasErrors() {
return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
}
if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
if stepDiags.HasErrors() { if stepDiags.HasErrors() {
return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err()) return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
} }
if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
log.Printf("[WARN] Config warnings:\n%s", stepDiags) if stepDiags.HasErrors() {
} return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
// Refresh!
state, stepDiags = ctx.Refresh()
if stepDiags.HasErrors() {
return state, fmt.Errorf("Error refreshing: %s", stepDiags.Err())
}
// If this step is a PlanOnly step, skip over this first Plan and subsequent
// Apply, and use the follow up Plan that checks for perpetual diffs
if !step.PlanOnly {
// Plan!
if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
return state, fmt.Errorf("Error planning: %s", stepDiags.Err())
} else {
log.Printf("[WARN] Test: Step plan: %s", p)
}
// We need to keep a copy of the state prior to destroying
// such that destroy steps can verify their behaviour in the check
// function
stateBeforeApplication := state.DeepCopy()
// Apply the diff, creating real resources.
state, stepDiags = ctx.Apply()
if stepDiags.HasErrors() {
return state, fmt.Errorf("Error applying: %s", stepDiags.Err())
}
// Run any configured checks
if step.Check != nil {
if step.Destroy {
if err := step.Check(stateBeforeApplication); err != nil {
return state, fmt.Errorf("Check failed: %s", err)
}
} else {
if err := step.Check(state); err != nil {
return state, fmt.Errorf("Check failed: %s", err)
}
} }
}
}
// Now, verify that Plan is now empty and we don't have a perpetual diff issue log.Printf("[WARN] Config warnings:\n%s", stepDiags)
// We do this with TWO plans. One without a refresh.
var p *terraform.Plan
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
return state, fmt.Errorf("Error on follow-up plan: %s", stepDiags.Err())
}
if p.Diff != nil && !p.Diff.Empty() {
if step.ExpectNonEmptyPlan {
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
} else {
return state, fmt.Errorf(
"After applying this step, the plan was not empty:\n\n%s", p)
} }
}
// And another after a Refresh. // Refresh!
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
state, stepDiags = ctx.Refresh() state, stepDiags = ctx.Refresh()
if stepDiags.HasErrors() { if stepDiags.HasErrors() {
return state, fmt.Errorf("Error on follow-up refresh: %s", stepDiags.Err()) return state, fmt.Errorf("Error refreshing: %s", stepDiags.Err())
} }
}
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
return state, fmt.Errorf("Error on second follow-up plan: %s", stepDiags.Err())
}
empty := p.Diff == nil || p.Diff.Empty()
// Data resources are tricky because they legitimately get instantiated // If this step is a PlanOnly step, skip over this first Plan and subsequent
// during refresh so that they will be already populated during the // Apply, and use the follow up Plan that checks for perpetual diffs
// plan walk. Because of this, if we have any data resources in the if !step.PlanOnly {
// config we'll end up wanting to destroy them again here. This is // Plan!
// acceptable and expected, and we'll treat it as "empty" for the if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
// sake of this testing. return state, fmt.Errorf("Error planning: %s", stepDiags.Err())
if step.Destroy { } else {
empty = true log.Printf("[WARN] Test: Step plan: %s", p)
}
for _, moduleDiff := range p.Diff.Modules { // We need to keep a copy of the state prior to destroying
for k, instanceDiff := range moduleDiff.Resources { // such that destroy steps can verify their behaviour in the check
if !strings.HasPrefix(k, "data.") { // function
empty = false stateBeforeApplication := state.DeepCopy()
break
}
if !instanceDiff.Destroy { // Apply the diff, creating real resources.
empty = false state, stepDiags = ctx.Apply()
if stepDiags.HasErrors() {
return state, fmt.Errorf("Error applying: %s", stepDiags.Err())
}
// Run any configured checks
if step.Check != nil {
if step.Destroy {
if err := step.Check(stateBeforeApplication); err != nil {
return state, fmt.Errorf("Check failed: %s", err)
}
} else {
if err := step.Check(state); err != nil {
return state, fmt.Errorf("Check failed: %s", err)
}
} }
} }
} }
}
if !empty { // Now, verify that Plan is now empty and we don't have a perpetual diff issue
if step.ExpectNonEmptyPlan { // We do this with TWO plans. One without a refresh.
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) var p *terraform.Plan
} else { if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
return state, fmt.Errorf( return state, fmt.Errorf("Error on follow-up plan: %s", stepDiags.Err())
"After applying this step and refreshing, "+ }
"the plan was not empty:\n\n%s", p) if p.Diff != nil && !p.Diff.Empty() {
if step.ExpectNonEmptyPlan {
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
} else {
return state, fmt.Errorf(
"After applying this step, the plan was not empty:\n\n%s", p)
}
} }
}
// Made it here, but expected a non-empty plan, fail! // And another after a Refresh.
if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") state, stepDiags = ctx.Refresh()
} if stepDiags.HasErrors() {
return state, fmt.Errorf("Error on follow-up refresh: %s", stepDiags.Err())
}
}
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
return state, fmt.Errorf("Error on second follow-up plan: %s", stepDiags.Err())
}
empty := p.Diff == nil || p.Diff.Empty()
// Made it here? Good job test step! // Data resources are tricky because they legitimately get instantiated
return state, nil // during refresh so that they will be already populated during the
// plan walk. Because of this, if we have any data resources in the
// config we'll end up wanting to destroy them again here. This is
// acceptable and expected, and we'll treat it as "empty" for the
// sake of this testing.
if step.Destroy {
empty = true
for _, moduleDiff := range p.Diff.Modules {
for k, instanceDiff := range moduleDiff.Resources {
if !strings.HasPrefix(k, "data.") {
empty = false
break
}
if !instanceDiff.Destroy {
empty = false
}
}
}
}
if !empty {
if step.ExpectNonEmptyPlan {
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
} else {
return state, fmt.Errorf(
"After applying this step and refreshing, "+
"the plan was not empty:\n\n%s", p)
}
}
// Made it here, but expected a non-empty plan, fail!
if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) {
return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
}
// Made it here? Good job test step!
return state, nil
*/
} }
func testStepTaint(state *terraform.State, step TestStep) error { func testStepTaint(state *terraform.State, step TestStep) error {

View File

@ -3,15 +3,12 @@ package resource
import ( import (
"fmt" "fmt"
"log" "log"
"reflect"
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax" "github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -52,7 +49,7 @@ func testStepImportState(
} }
opts.Config = cfg opts.Config = cfg
opts.State = terraform.NewState() opts.State = states.NewState()
ctx, stepDiags := terraform.NewContext(&opts) ctx, stepDiags := terraform.NewContext(&opts)
if stepDiags.HasErrors() { if stepDiags.HasErrors() {
return state, stepDiags.Err() return state, stepDiags.Err()
@ -89,77 +86,84 @@ func testStepImportState(
// Go through the new state and verify // Go through the new state and verify
if step.ImportStateCheck != nil { if step.ImportStateCheck != nil {
var states []*terraform.InstanceState var states []*states.ResourceInstanceObjectSrc
for _, r := range newState.RootModule().Resources { for _, r := range newState.RootModule().Resources {
if r.Primary != nil { for _, i := range r.Instances {
states = append(states, r.Primary) if i.Current != nil {
states = append(states, i.Current)
}
} }
} }
if err := step.ImportStateCheck(states); err != nil { // TODO: update for new state types
return nil, fmt.Errorf("ImportStateCheck call in testStepImportState not yet updated for new state types")
/*if err := step.ImportStateCheck(states); err != nil {
return state, err return state, err
} }*/
} }
// Verify that all the states match // Verify that all the states match
if step.ImportStateVerify { if step.ImportStateVerify {
new := newState.RootModule().Resources return nil, fmt.Errorf("testStepImportStep ImportStateVerify not yet updated for new state types")
old := state.RootModule().Resources /*
for _, r := range new { new := newState.RootModule().Resources
// Find the existing resource old := state.RootModule().Resources
var oldR *terraform.ResourceState for _, r := range new {
for _, r2 := range old { // Find the existing resource
if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type { var oldR *terraform.ResourceState
oldR = r2 for _, r2 := range old {
break if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type {
} oldR = r2
} break
if oldR == nil {
return state, fmt.Errorf(
"Failed state verification, resource with ID %s not found",
r.Primary.ID)
}
// Compare their attributes
actual := make(map[string]string)
for k, v := range r.Primary.Attributes {
actual[k] = v
}
expected := make(map[string]string)
for k, v := range oldR.Primary.Attributes {
expected[k] = v
}
// Remove fields we're ignoring
for _, v := range step.ImportStateVerifyIgnore {
for k, _ := range actual {
if strings.HasPrefix(k, v) {
delete(actual, k)
} }
} }
for k, _ := range expected { if oldR == nil {
if strings.HasPrefix(k, v) { return state, fmt.Errorf(
delete(expected, k) "Failed state verification, resource with ID %s not found",
} r.Primary.ID)
} }
}
if !reflect.DeepEqual(actual, expected) { // Compare their attributes
// Determine only the different attributes actual := make(map[string]string)
for k, v := range expected { for k, v := range r.Primary.Attributes {
if av, ok := actual[k]; ok && v == av { actual[k] = v
delete(expected, k) }
delete(actual, k) expected := make(map[string]string)
for k, v := range oldR.Primary.Attributes {
expected[k] = v
}
// Remove fields we're ignoring
for _, v := range step.ImportStateVerifyIgnore {
for k, _ := range actual {
if strings.HasPrefix(k, v) {
delete(actual, k)
}
}
for k, _ := range expected {
if strings.HasPrefix(k, v) {
delete(expected, k)
}
} }
} }
spewConf := spew.NewDefaultConfig() if !reflect.DeepEqual(actual, expected) {
spewConf.SortKeys = true // Determine only the different attributes
return state, fmt.Errorf( for k, v := range expected {
"ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ if av, ok := actual[k]; ok && v == av {
"\n\n%s\n\n%s", delete(expected, k)
spewConf.Sdump(actual), spewConf.Sdump(expected)) delete(actual, k)
}
}
spewConf := spew.NewDefaultConfig()
spewConf.SortKeys = true
return state, fmt.Errorf(
"ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
"\n\n%s\n\n%s",
spewConf.Sdump(actual), spewConf.Sdump(expected))
}
} }
} */
} }
// Return the old state (non-imported) so we don't change anything. // Return the old state (non-imported) so we don't change anything.

View File

@ -16,7 +16,6 @@ import (
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/svchost/disco" "github.com/hashicorp/terraform/svchost/disco"
"github.com/hashicorp/terraform/terraform"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -113,9 +112,6 @@ func init() {
func wrappedMain() int { func wrappedMain() int {
var err error var err error
// We always need to close the DebugInfo before we exit.
defer terraform.CloseDebugInfo()
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
log.Printf( log.Printf(
"[INFO] Terraform version: %s %s %s", "[INFO] Terraform version: %s %s %s",

View File

@ -23,6 +23,38 @@ func NewChanges() *Changes {
} }
} }
func (c *Changes) Empty() bool {
return (len(c.Resources) + len(c.RootOutputs)) == 0
}
// ResourceInstance returns the planned change for the current object of the
// resource instance of the given address, if any. Returns nil if no change is
// planned.
func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc {
addrStr := addr.String()
for _, rc := range c.Resources {
if rc.Addr.String() == addrStr && rc.DeposedKey == states.NotDeposed {
return rc
}
}
return nil
}
// ResourceInstanceDeposed returns the plan change of a deposed object of
// the resource instance of the given address, if any. Returns nil if no change
// is planned.
func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc {
addrStr := addr.String()
for _, rc := range c.Resources {
if rc.Addr.String() == addrStr && rc.DeposedKey == key {
return rc
}
}
return nil
}
// ResourceInstanceChange describes a change to a particular resource instance // ResourceInstanceChange describes a change to a particular resource instance
// object. // object.
type ResourceInstanceChange struct { type ResourceInstanceChange struct {

18
plans/changes_sync.go Normal file
View File

@ -0,0 +1,18 @@
package plans
import (
"sync"
)
// ChangesSync is a wrapper around a Changes that provides a concurrency-safe
// interface to insert new changes and retrieve copies of existing changes.
//
// Each ChangesSync is independent of all others, so all concurrent writers
// to a particular Changes must share a single ChangesSync. Behavior is
// undefined if any other caller makes changes to the underlying Changes
// object or its nested objects concurrently with any of the methods of a
// particular ChangesSync.
type ChangesSync struct {
lock sync.Mutex
changes *Changes
}

View File

@ -69,3 +69,17 @@ func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) {
return ctymsgpack.Unmarshal([]byte(v), ty) return ctymsgpack.Unmarshal([]byte(v), ty)
} }
// ImpliedType returns the type implied by the serialized structure of the
// receiving value.
//
// This will not necessarily be exactly the type that was given when the
// value was encoded, and in particular must not be used for values that
// were encoded with their static type given as cty.DynamicPseudoType.
// It is however safe to use this method for values that were encoded using
// their runtime type as the conforming type, with the result being
// semantically equivalent but with all lists and sets represented as tuples,
// and maps as objects, due to ambiguities of the serialization.
func (v DynamicValue) ImpliedType() (cty.Type, error) {
return ctymsgpack.ImpliedType([]byte(v))
}

View File

@ -4,6 +4,8 @@ import (
"sort" "sort"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/zclconf/go-cty/cty"
) )
// Plan is the top-level type representing a planned set of changes. // Plan is the top-level type representing a planned set of changes.
@ -44,6 +46,19 @@ type Backend struct {
Workspace string Workspace string
} }
func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) {
dv, err := NewDynamicValue(config, configSchema.ImpliedType())
if err != nil {
return nil, err
}
return &Backend{
Type: typeName,
Config: dv,
Workspace: workspaceName,
}, nil
}
// ProviderAddrs returns a list of all of the provider configuration addresses // ProviderAddrs returns a list of all of the provider configuration addresses
// referenced throughout the receiving plan. // referenced throughout the receiving plan.
// //

View File

@ -13,8 +13,8 @@ func TestProviderAddrs(t *testing.T) {
plan := &Plan{ plan := &Plan{
VariableValues: map[string]DynamicValue{}, VariableValues: map[string]DynamicValue{},
Changes: &Changes{ Changes: &Changes{
RootOutputs: map[string]*OutputChange{}, RootOutputs: map[string]*OutputChangeSrc{},
Resources: []*ResourceInstanceChange{ Resources: []*ResourceInstanceChangeSrc{
{ {
Addr: addrs.Resource{ Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode, Mode: addrs.ManagedResourceMode,

View File

@ -7,12 +7,10 @@ import (
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/plans"
version "github.com/hashicorp/go-version" version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile" "github.com/hashicorp/terraform/states/statefile"
) )
@ -45,8 +43,8 @@ func TestRoundtrip(t *testing.T) {
// file is tested more fully in tfplan_test.go . // file is tested more fully in tfplan_test.go .
planIn := &plans.Plan{ planIn := &plans.Plan{
Changes: &plans.Changes{ Changes: &plans.Changes{
Resources: []*plans.ResourceInstanceChange{}, Resources: []*plans.ResourceInstanceChangeSrc{},
RootOutputs: map[string]*plans.OutputChange{}, RootOutputs: map[string]*plans.OutputChangeSrc{},
}, },
ProviderSHA256s: map[string][]byte{}, ProviderSHA256s: map[string][]byte{},
VariableValues: map[string]plans.DynamicValue{ VariableValues: map[string]plans.DynamicValue{

View File

@ -21,16 +21,16 @@ func TestTFPlanRoundTrip(t *testing.T) {
"foo": mustNewDynamicValueStr("foo value"), "foo": mustNewDynamicValueStr("foo value"),
}, },
Changes: &plans.Changes{ Changes: &plans.Changes{
RootOutputs: map[string]*plans.OutputChange{ RootOutputs: map[string]*plans.OutputChangeSrc{
"bar": { "bar": {
Change: plans.Change{ ChangeSrc: plans.ChangeSrc{
Action: plans.Create, Action: plans.Create,
After: mustNewDynamicValueStr("bar value"), After: mustNewDynamicValueStr("bar value"),
}, },
Sensitive: false, Sensitive: false,
}, },
"baz": { "baz": {
Change: plans.Change{ ChangeSrc: plans.ChangeSrc{
Action: plans.NoOp, Action: plans.NoOp,
Before: mustNewDynamicValueStr("baz value"), Before: mustNewDynamicValueStr("baz value"),
After: mustNewDynamicValueStr("baz value"), After: mustNewDynamicValueStr("baz value"),
@ -38,7 +38,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
Sensitive: false, Sensitive: false,
}, },
"secret": { "secret": {
Change: plans.Change{ ChangeSrc: plans.ChangeSrc{
Action: plans.Update, Action: plans.Update,
Before: mustNewDynamicValueStr("old secret value"), Before: mustNewDynamicValueStr("old secret value"),
After: mustNewDynamicValueStr("new secret value"), After: mustNewDynamicValueStr("new secret value"),
@ -46,7 +46,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
Sensitive: true, Sensitive: true,
}, },
}, },
Resources: []*plans.ResourceInstanceChange{ Resources: []*plans.ResourceInstanceChangeSrc{
{ {
Addr: addrs.Resource{ Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode, Mode: addrs.ManagedResourceMode,
@ -56,7 +56,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
ProviderAddr: addrs.ProviderConfig{ ProviderAddr: addrs.ProviderConfig{
Type: "test", Type: "test",
}.Absolute(addrs.RootModuleInstance), }.Absolute(addrs.RootModuleInstance),
Change: plans.Change{ ChangeSrc: plans.ChangeSrc{
Action: plans.Replace, Action: plans.Replace,
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo-bar-baz"), "id": cty.StringVal("foo-bar-baz"),
@ -76,7 +76,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
ProviderAddr: addrs.ProviderConfig{ ProviderAddr: addrs.ProviderConfig{
Type: "test", Type: "test",
}.Absolute(addrs.RootModuleInstance), }.Absolute(addrs.RootModuleInstance),
Change: plans.Change{ ChangeSrc: plans.ChangeSrc{
Action: plans.Delete, Action: plans.Delete,
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("bar-baz-foo"), "id": cty.StringVal("bar-baz-foo"),

View File

@ -81,7 +81,7 @@ type GetSchemaResponse struct {
// Schema pairs a provider or resource schema with that schema's version. // Schema pairs a provider or resource schema with that schema's version.
// This is used to be able to upgrade the schema in UpgradeResourceState. // This is used to be able to upgrade the schema in UpgradeResourceState.
type Schema struct { type Schema struct {
Version int Version uint64
Block *configschema.Block Block *configschema.Block
} }

View File

@ -3,7 +3,8 @@ package state
import ( import (
"sync" "sync"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
) )
// BackupState wraps a State that backs up the state on the first time that // BackupState wraps a State that backs up the state on the first time that
@ -18,7 +19,7 @@ type BackupState struct {
done bool done bool
} }
func (s *BackupState) State() *terraform.State { func (s *BackupState) State() *states.State {
return s.Real.State() return s.Real.State()
} }
@ -26,7 +27,7 @@ func (s *BackupState) RefreshState() error {
return s.Real.RefreshState() return s.Real.RefreshState()
} }
func (s *BackupState) WriteState(state *terraform.State) error { func (s *BackupState) WriteState(state *states.State) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -74,7 +75,7 @@ func (s *BackupState) backup() error {
// purposes, but we don't need a backup or lock if the state is empty, so // purposes, but we don't need a backup or lock if the state is empty, so
// skip this with a nil state. // skip this with a nil state.
if state != nil { if state != nil {
ls := &LocalState{Path: s.Path} ls := statemgr.NewFilesystem(s.Path)
if err := ls.WriteState(state); err != nil { if err := ls.WriteState(state); err != nil {
return err return err
} }

Some files were not shown because too many files have changed in this diff Show More