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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
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},
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
},
Remaining: hcl.Traversal{},
},
``,
},

View File

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

View File

@ -13,10 +13,13 @@ import (
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/configs"
"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/tfdiags"
)
@ -25,25 +28,18 @@ import (
// backend must have. This state cannot be deleted.
const DefaultStateName = "default"
// This must be returned rather than a custom error so that the Terraform
// CLI can detect it and handle it appropriately.
var (
// ErrDefaultStateNotSupported is returned when an operation does not support
// using the default state, but requires a named state to be selected.
ErrDefaultStateNotSupported = errors.New("default state not supported\n" +
"You can create a new workspace with the \"workspace new\" command.")
// ErrWorkspacesNotSupported is an error returned when a caller attempts
// to perform an operation on a workspace other than "default" for a
// backend that doesn't support multiple workspaces.
//
// The caller can detect this to do special fallback behavior or produce
// a specific, helpful error message.
var ErrWorkspacesNotSupported = errors.New("workspaces not supported")
// ErrNamedStatesNotSupported is returned when a named state operation
// isn't supported.
ErrNamedStatesNotSupported = errors.New("named states not supported")
// 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
// ErrNamedStatesNotSupported is an older name for ErrWorkspacesNotSupported.
//
// Deprecated: Use ErrWorkspacesNotSupported instead.
var ErrNamedStatesNotSupported = ErrWorkspacesNotSupported
// Backend is the minimal interface that must be implemented to enable Terraform.
type Backend interface {
@ -87,25 +83,26 @@ type Backend interface {
// is undefined and no other methods may be called.
Configure(cty.Value) tfdiags.Diagnostics
// State returns the current state for this environment. This state may
// 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.
// StateMgr returns the state manager for the given workspace name.
//
// If the named state doesn't exist it will be created. The "default" state
// is always assumed to exist.
State(name string) (state.State, error)
// DeleteState removes the named state if it exists. It is an error
// to delete the default state.
// If the returned state manager also implements statemgr.Locker then
// it's the caller's responsibility to call Lock and Unlock as appropriate.
//
// DeleteState does not prevent deleting a state that is in use. It is the
// responsibility of the caller to hold a Lock on the state when calling
// this method.
DeleteState(name string) error
// If the named workspace doesn't exist, or if it has no state, it will
// be created either immediately on this call or the first time
// PersistState is called, depending on the state manager implementation.
StateMgr(workspace string) (statemgr.Full, error)
// States returns a list of configured named states.
States() ([]string, error)
// DeleteWorkspace removes the workspace with the given name if it exists.
//
// 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.
@ -136,7 +133,7 @@ type Enhanced interface {
type Local interface {
// Context returns a runnable terraform Context. The operation parameter
// 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.
@ -166,7 +163,7 @@ type Operation struct {
PlanId string
PlanRefresh bool // PlanRefresh will do a refresh before a 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
// root module.
@ -178,11 +175,10 @@ type Operation struct {
// 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 *terraform.Plan
PlanFile *planfile.Reader
// The options below are more self-explanatory and affect the runtime
// behavior of the operation.
AutoApprove bool
Destroy bool
Targets []addrs.Targetable
Variables map[string]UnparsedVariableValue
@ -259,7 +255,7 @@ type RunningOperation struct {
// State is the final state after the operation completed. Persisting
// this state is managed by the backend. This should only be read
// after the operation completes to avoid read/write races.
State *terraform.State
State *states.State
}
// 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/configs/configschema"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
"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
// 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
// Operation. See Operation for more details.
@ -97,8 +97,11 @@ type Local struct {
RunningInAutomation bool
opLock sync.Mutex
once sync.Once
}
var _ backend.Backend = (*Local)(nil)
func (b *Local) ConfigSchema() *configschema.Block {
if b.Backend != nil {
return b.Backend.ConfigSchema()
@ -184,67 +187,10 @@ func (b *Local) Configure(obj cty.Value) tfdiags.Diagnostics {
return diags
}
func (b *Local) State(name string) (state.State, 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 {
func (b *Local) Workspaces() ([]string, error) {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.DeleteState(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) States() ([]string, error) {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.States()
return b.Backend.Workspaces()
}
// the listing always start with "default"
@ -272,6 +218,55 @@ func (b *Local) States() ([]string, error) {
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
//
// 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
}
// 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.
func (b *Local) opWait(
doneCh <-chan struct{},
stopCtx context.Context,
cancelCtx context.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
// we can handle it properly.
select {
@ -365,7 +360,7 @@ func (b *Local) opWait(
// try to force a PersistState just in case the process is terminated
// 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.
// If this isn't transient, we will catch it again below, and
// 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
// 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
stateOutPath := b.StateOutPath
backupPath := b.StateBackupPath

View File

@ -8,9 +8,12 @@ import (
"log"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend"
"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/tfdiags"
)
@ -23,10 +26,11 @@ func (b *Local) opApply(
log.Printf("[INFO] backend/local: starting Apply operation")
var diags tfdiags.Diagnostics
var err error
// If we have a nil module at this point, then set it to an empty tree
// 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(
tfdiags.Error,
"No configuration files",
@ -47,9 +51,9 @@ func (b *Local) opApply(
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
// Get our context
tfCtx, opState, err := b.context(op)
if err != nil {
diags = diags.Append(err)
tfCtx, _, opState, contextDiags := b.context(op)
diags = diags.Append(contextDiags)
if contextDiags.HasErrors() {
b.ReportResult(runningOp, diags)
return
}
@ -58,7 +62,7 @@ func (b *Local) opApply(
runningOp.State = tfCtx.State()
// 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 op.PlanRefresh {
log.Printf("[INFO] backend/local: apply calling Refresh")
@ -80,7 +84,7 @@ func (b *Local) opApply(
return
}
dispPlan := format.NewPlan(plan)
dispPlan := format.NewPlan(plan.Changes)
trivialPlan := dispPlan.Empty()
hasUI := op.UIOut != nil && op.UIIn != nil
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
stateHook.State = opState
stateHook.StateMgr = opState
// Start the apply in a goroutine so that we can be interrupted.
var applyState *terraform.State
var applyState *states.State
var applyDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
@ -152,14 +156,8 @@ func (b *Local) opApply(
// Store the final state
runningOp.State = applyState
// Persist the state
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 {
err = statemgr.WriteAndPersist(opState, applyState)
if err != nil {
diags = diags.Append(b.backupStateForError(applyState, err))
b.ReportResult(runningOp, diags)
return
@ -211,10 +209,10 @@ func (b *Local) opApply(
// 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
// 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))
local := &state.LocalState{Path: "errored.tfstate"}
local := statemgr.NewFilesystem("errored.tfstate")
writeErr := local.WriteState(applyState)
if writeErr != nil {
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
// here for some reason.
stateBuf := new(bytes.Buffer)
jsonErr := terraform.WriteState(applyState, stateBuf)
stateFile := &statefile.File{
State: applyState,
}
jsonErr := statefile.Write(stateFile, stateBuf)
if jsonErr != nil {
b.CLI.Error(fmt.Sprintf(
"Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr,

View File

@ -10,13 +10,15 @@ import (
"sync"
"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/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) {
@ -226,19 +228,17 @@ type backendWithFailingState struct {
Local
}
func (b *backendWithFailingState) State(name string) (state.State, error) {
func (b *backendWithFailingState) StateMgr(name string) (statemgr.Full, error) {
return &failingState{
&state.LocalState{
Path: "failing-state.tfstate",
},
statemgr.NewFilesystem("failing-state.tfstate"),
}, nil
}
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")
}

View File

@ -2,18 +2,21 @@ package local
import (
"context"
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend"
"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/tfdiags"
)
// 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
// to ask for input/validate.
op.Type = backend.OperationTypeInvalid
@ -24,27 +27,26 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State,
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
// Get the state.
s, err := b.State(op.Workspace)
// Get the latest state.
s, err := b.StateMgr(op.Workspace)
if err != nil {
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 {
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 {
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
return nil, nil, diags
return nil, nil, nil, diags
}
// Initialize our context options
@ -58,8 +60,55 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
opts.Targets = op.Targets
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.
config, configDiags := op.ConfigLoader.LoadConfig(op.ConfigDir)
config, configSnap, configDiags := op.ConfigLoader.LoadConfigWithSnapshot(op.ConfigDir)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, nil, diags
@ -75,50 +124,80 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
opts.Variables = variables
}
// Load our state
// 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)
}
tfCtx, ctxDiags := terraform.NewContext(&opts)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
return nil, nil, diags
return tfCtx, configSnap, 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
// here since every option requires this.
if op.Type != backend.OperationTypeInvalid {
// If input asking is enabled, then do that
if op.Plan == 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, diags
}
variables := terraform.InputValues{}
for name, dyVal := range plan.VariableValues {
ty, err := dyVal.ImpliedType()
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
}
val, err := dyVal.Decode(ty)
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
if b.OpValidation {
validateDiags := tfCtx.Validate()
diags = diags.Append(validateDiags)
variables[name] = &terraform.InputValue{
Value: val,
SourceType: terraform.ValueFromPlan,
}
}
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 = `

View File

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

View File

@ -9,7 +9,8 @@ import (
"github.com/hashicorp/errwrap"
"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"
)
@ -42,7 +43,7 @@ func (b *Local) opRefresh(
}
// Get our context
tfCtx, opState, contextDiags := b.context(op)
tfCtx, _, opState, contextDiags := b.context(op)
diags = diags.Append(contextDiags)
if contextDiags.HasErrors() {
b.ReportResult(runningOp, diags)
@ -51,15 +52,19 @@ func (b *Local) opRefresh(
// Set our state
runningOp.State = opState.State()
if runningOp.State.Empty() || !runningOp.State.HasResources() {
if !runningOp.State.HasResources() {
if b.CLI != nil {
b.CLI.Output(b.Colorize().Color(
strings.TrimSpace(refreshNoState) + "\n"))
diags = diags.Append(tfdiags.Sourceless(
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
var newState *terraform.State
var newState *states.State
var refreshDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
@ -80,17 +85,12 @@ func (b *Local) opRefresh(
return
}
// Write and persist the state
if err := opState.WriteState(newState); err != nil {
err := statemgr.WriteAndPersist(opState, newState)
if err != nil {
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
b.ReportResult(runningOp, diags)
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 = `

View File

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

View File

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

View File

@ -4,6 +4,11 @@ import (
"reflect"
"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"
)
@ -18,10 +23,14 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
"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(n, d)
h.PostDiff(addr, states.DeposedKey("deadbeef"), plans.Delete, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -31,8 +40,7 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
expected.ToRemove = 1
if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.",
expected, h)
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
}
}
@ -46,10 +54,14 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
"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(n, d)
h.PostDiff(addr, states.CurrentGen, plans.Delete, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -59,8 +71,7 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
expected.ToRemove = 4
if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.",
expected, h)
t.Fatalf("Expected %#v, got %#v instead.", 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(n, d)
h.PostDiff(addr, states.CurrentGen, plans.Create, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -98,8 +113,7 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
expected.ToRemove = 0
if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.",
expected, h)
t.Fatalf("Expected %#v, got %#v instead.", 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(n, d)
h.PostDiff(addr, states.CurrentGen, plans.Update, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -140,32 +158,28 @@ func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
expected.ToRemove = 0
if !reflect.DeepEqual(expected, h) {
t.Fatalf("Expected %#v, got %#v instead.",
expected, h)
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
}
}
func TestCountHookPostDiff_Mixed(t *testing.T) {
h := new(CountHook)
resources := map[string]*terraform.InstanceDiff{
"foo": &terraform.InstanceDiff{
Destroy: true,
},
"bar": &terraform.InstanceDiff{},
"lorem": &terraform.InstanceDiff{
Destroy: false,
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{},
},
},
"ipsum": &terraform.InstanceDiff{Destroy: true},
resources := map[string]plans.Action{
"foo": plans.Delete,
"bar": plans.NoOp,
"lorem": plans.Update,
"ipsum": plans.Delete,
}
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(n, d)
h.PostDiff(addr, states.CurrentGen, a, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -190,10 +204,14 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
"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(n, d)
h.PostDiff(addr, states.CurrentGen, plans.NoOp, cty.DynamicVal, cty.DynamicVal)
}
expected := new(CountHook)
@ -211,23 +229,21 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
func TestCountHookPostDiff_DataSource(t *testing.T) {
h := new(CountHook)
resources := map[string]*terraform.InstanceDiff{
"data.foo": &terraform.InstanceDiff{
Destroy: true,
},
"data.bar": &terraform.InstanceDiff{},
"data.lorem": &terraform.InstanceDiff{
Destroy: false,
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{},
},
},
"data.ipsum": &terraform.InstanceDiff{Destroy: true},
resources := map[string]plans.Action{
"foo": plans.Delete,
"bar": plans.NoOp,
"lorem": plans.Update,
"ipsum": plans.Delete,
}
for k, d := range resources {
n := &terraform.InstanceInfo{Id: k}
h.PostDiff(n, d)
for k, a := range resources {
addr := addrs.Resource{
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)

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import (
"testing"
"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/tfdiags"
)
@ -97,12 +97,12 @@ type TestLocalSingleState struct {
*Local
}
func (b *TestLocalSingleState) State(name string) (state.State, error) {
func (b *TestLocalSingleState) State(name string) (statemgr.Full, error) {
if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported
}
return b.Local.State(name)
return b.Local.StateMgr(name)
}
func (b *TestLocalSingleState) States() ([]string, error) {

View File

@ -2,7 +2,7 @@ package backend
import (
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
@ -25,15 +25,16 @@ func (Nil) Configure(cty.Value) tfdiags.Diagnostics {
return nil
}
func (Nil) State(string) (state.State, error) {
// We have to return a non-nil state to adhere to the interface
return &state.InmemState{}, nil
func (Nil) StateMgr(string) (statemgr.Full, error) {
// We must return a non-nil manager to adhere to the interface, so
// 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
}
func (Nil) States() ([]string, error) {
func (Nil) Workspaces() ([]string, error) {
return []string{DefaultStateName}, nil
}

View File

@ -82,15 +82,15 @@ func (b *Backend) configure(ctx context.Context) error {
return nil
}
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported
}
func (b *Backend) DeleteState(string) error {
func (b *Backend) DeleteWorkspace(string) error {
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 {
return nil, backend.ErrNamedStatesNotSupported
}

View File

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

View File

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

View File

@ -2,18 +2,19 @@ package azure
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"encoding/base64"
"github.com/Azure/azure-sdk-for-go/storage"
multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/states"
)
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")
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)
}
if err := stateMgr.PersistState(); err != nil {

View File

@ -6,12 +6,13 @@ package remotestate
import (
"context"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// 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)
}
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported
}
func (b *Backend) DeleteState(name string) error {
func (b *Backend) DeleteWorkspace(name string) error {
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
if b.client == nil {
panic("nil remote client")

View File

@ -7,14 +7,15 @@ import (
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
)
const (
keyEnvPrefix = "-env:"
)
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
// List our raw path
prefix := b.configData.Get("path").(string) + keyEnvPrefix
keys, _, err := b.client.KV().Keys(prefix, "/", nil)
@ -49,7 +50,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteState(name string) error {
func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
@ -63,7 +64,7 @@ func (b *Backend) DeleteState(name string) error {
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
path := b.path(name)
@ -71,7 +72,7 @@ func (b *Backend) State(name string) (state.State, error) {
gzip := b.configData.Get("gzip").(bool)
// Build the state client
var stateMgr state.State = &remote.State{
var stateMgr = &remote.State{
Client: &RemoteClient{
Client: b.client,
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 {
stateMgr = &state.LockDisabled{Inner: stateMgr}
stateMgr.DisableLocks()
}
// 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 v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err)
return nil, err
}

View File

@ -75,15 +75,15 @@ func (b *Backend) configure(ctx context.Context) error {
return nil
}
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported
}
func (b *Backend) DeleteState(string) error {
func (b *Backend) DeleteWorkspace(string) error {
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 {
return nil, backend.ErrNamedStatesNotSupported
}

View File

@ -7,13 +7,14 @@ import (
"strings"
etcdv3 "github.com/coreos/etcd/clientv3"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"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())
if err != nil {
return nil, err
@ -29,7 +30,7 @@ func (b *Backend) States() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteState(name string) error {
func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("Can't delete default state.")
}
@ -40,7 +41,7 @@ func (b *Backend) DeleteState(name string) error {
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{
Client: &RemoteClient{
Client: b.client,
@ -73,7 +74,7 @@ func (b *Backend) State(name string) (state.State, error) {
}
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)
return nil, err
}

View File

@ -7,11 +7,12 @@ import (
"strings"
"cloud.google.com/go/storage"
"google.golang.org/api/iterator"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/iterator"
"github.com/hashicorp/terraform/states"
)
const (
@ -19,9 +20,9 @@ const (
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.
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
states := []string{backend.DefaultStateName}
bucket := b.storageClient.Bucket(b.bucketName)
@ -53,8 +54,8 @@ func (b *Backend) States() ([]string, error) {
return states, nil
}
// DeleteState deletes the named state. The "default" state cannot be deleted.
func (b *Backend) DeleteState(name string) error {
// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string) error {
if name == backend.DefaultStateName {
return fmt.Errorf("cowardly refusing to delete the %q state", name)
}
@ -83,9 +84,9 @@ func (b *Backend) client(name string) (*remoteClient, error) {
}, 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.
func (b *Backend) State(name string) (state.State, error) {
func (b *Backend) StateMgr(name string) (state.State, error) {
c, err := b.client(name)
if err != nil {
return nil, err
@ -127,7 +128,7 @@ func (b *Backend) State(name string) (state.State, error) {
return baseErr
}
if err := st.WriteState(terraform.NewState()); err != nil {
if err := st.WriteState(states.NewState()); err != nil {
return nil, unlock(err)
}
if err := st.PersistState(); err != nil {

View File

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

View File

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/state"
"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
@ -87,7 +87,7 @@ func (b *Backend) configure(ctx context.Context) error {
return nil
}
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
states.Lock()
defer states.Unlock()
@ -101,7 +101,7 @@ func (b *Backend) States() ([]string, error) {
return workspaces, nil
}
func (b *Backend) DeleteState(name string) error {
func (b *Backend) DeleteWorkspace(name string) error {
states.Lock()
defer states.Unlock()
@ -113,7 +113,7 @@ func (b *Backend) DeleteState(name string) error {
return nil
}
func (b *Backend) State(name string) (state.State, error) {
func (b *Backend) StateMgr(name string) (state.State, error) {
states.Lock()
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 v := s.State(); v == nil {
if err := s.WriteState(terraform.NewState()); err != nil {
if err := s.WriteState(statespkg.NewState()); err != nil {
return nil, err
}
if err := s.PersistState(); err != nil {

View File

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

View File

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

View File

@ -6,15 +6,15 @@ import (
"github.com/hashicorp/terraform/state/remote"
)
func (b *Backend) States() ([]string, error) {
func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported
}
func (b *Backend) DeleteState(name string) error {
func (b *Backend) DeleteWorkspace(name string) error {
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 {
return nil, backend.ErrNamedStatesNotSupported
}

View File

@ -5,18 +5,17 @@ import (
"sort"
"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"
"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/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
@ -67,31 +66,19 @@ func TestWrapConfig(raw map[string]interface{}) hcl.Body {
func TestBackendStates(t *testing.T, b Backend) {
t.Helper()
noDefault := false
if _, err := b.State(DefaultStateName); err != nil {
if err == ErrDefaultStateNotSupported {
noDefault = true
} 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)
workspaces, err := b.Workspaces()
if err == ErrNamedStatesNotSupported {
t.Logf("TestBackend: workspaces not supported in %T, skipping", b)
return
}
// Test it starts with only the default
if !noDefault && (len(states) != 1 || states[0] != DefaultStateName) {
t.Fatalf("should have default to start: %#v", states)
if len(workspaces) != 1 || workspaces[0] != DefaultStateName {
t.Fatalf("should only have default to start: %#v", workspaces)
}
// Create a couple states
foo, err := b.State("foo")
foo, err := b.StateMgr("foo")
if err != nil {
t.Fatalf("error: %s", err)
}
@ -102,7 +89,7 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v)
}
bar, err := b.State("bar")
bar, err := b.StateMgr("bar")
if err != nil {
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
{
// start with a fresh state, and record the lineage being
// written to "bar"
barState := terraform.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
// We'll use two distinct states here and verify that changing one
// does not also change the other.
barState := states.NewState()
fooState := states.NewState()
// write a known state to foo
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)
}
// 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
if err := bar.WriteState(barState); err != nil {
t.Fatalf("bad: %s", err)
@ -155,17 +147,12 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing foo:", err)
}
fooState = foo.State()
switch {
case fooState == nil:
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")
if fooState.HasResources() {
t.Fatal("after writing a resource to bar, foo now has resources too")
}
// fetch foo again from the backend
foo, err = b.State("foo")
foo, err = b.StateMgr("foo")
if err != nil {
t.Fatal("error re-fetching state:", err)
}
@ -173,15 +160,12 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing foo:", err)
}
fooState = foo.State()
switch {
case fooState == nil:
t.Fatal("nil state read from foo")
case fooState.Lineage != fooLineage:
t.Fatal("incorrect state returned from backend")
if fooState.HasResources() {
t.Fatal("after writing a resource to bar and re-reading foo, foo now has resources too")
}
// fetch the bar again from the backend
bar, err = b.State("bar")
bar, err = b.StateMgr("bar")
if err != nil {
t.Fatal("error re-fetching state:", err)
}
@ -189,46 +173,40 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatal("error refreshing bar:", err)
}
barState = bar.State()
switch {
case barState == nil:
t.Fatal("nil state read from bar")
case barState.Lineage != barLineage:
t.Fatal("incorrect state returned from backend")
if !barState.HasResources() {
t.Fatal("after writing a resource instance object to bar and re-reading it, the object has vanished")
}
}
// Verify we can now list them
{
// we determined that named stated are supported earlier
states, err := b.States()
workspaces, err := b.Workspaces()
if err != nil {
t.Fatal(err)
}
sort.Strings(states)
sort.Strings(workspaces)
expected := []string{"bar", "default", "foo"}
if noDefault {
expected = []string{"bar", "foo"}
}
if !reflect.DeepEqual(states, expected) {
t.Fatalf("bad: %#v", states)
if !reflect.DeepEqual(workspaces, expected) {
t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected)
}
}
// Delete some states
if err := b.DeleteState("foo"); err != nil {
// Delete some workspaces
if err := b.DeleteWorkspace("foo"); err != nil {
t.Fatalf("err: %s", err)
}
// 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")
}
// 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
// preventing re-creation.
foo, err = b.State("foo")
foo, err = b.StateMgr("foo")
if err != nil {
t.Fatalf("error: %s", err)
}
@ -239,23 +217,20 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v)
}
// and delete it again
if err := b.DeleteState("foo"); err != nil {
if err := b.DeleteWorkspace("foo"); err != nil {
t.Fatalf("err: %s", err)
}
// Verify deletion
{
states, err := b.States()
if err == ErrNamedStatesNotSupported {
states, err := b.Workspaces()
if err == ErrWorkspacesNotSupported {
t.Logf("TestBackend: named states not supported in %T, skipping", b)
return
}
sort.Strings(states)
expected := []string{"bar", "default"}
if noDefault {
expected = []string{"bar"}
}
if !reflect.DeepEqual(states, expected) {
t.Fatalf("bad: %#v", states)
}
@ -282,7 +257,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
t.Helper()
// Get the default state for each
b1StateMgr, err := b1.State(DefaultStateName)
b1StateMgr, err := b1.StateMgr(DefaultStateName)
if err != nil {
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)
b2StateMgr, err := b2.State(DefaultStateName)
b2StateMgr, err := b2.StateMgr(DefaultStateName)
if err != nil {
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
// when locked. This should only happen when a state is loaded via the
// backend, and as a remote state.
_, err = b2.State(DefaultStateName)
_, err = b2.StateMgr(DefaultStateName)
if err != nil {
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")
}
infoErr, ok := err.(*state.LockError)
infoErr, ok := err.(*statemgr.LockError)
if !ok {
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

View File

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

View File

@ -11,9 +11,8 @@ import (
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/states"
"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(&refresh, "refresh", true, "refresh")
cmdFlags.IntVar(
&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "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.
if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
c.Ui.Error(fmt.Sprintf(
"Invalid path: %s", err))
c.Ui.Error(fmt.Sprintf("Invalid path: %s", err))
return 1
} else if !strings.HasPrefix(detected, "file") {
// 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
plan, err := c.Plan(configPath)
planFile, err := c.PlanFile(configPath)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if c.Destroy && plan != nil {
c.Ui.Error(fmt.Sprintf(
"Destroy can't be called with a plan file."))
if c.Destroy && planFile != nil {
c.Ui.Error(fmt.Sprintf("Destroy can't be called with a plan file."))
return 1
}
if plan != nil {
if planFile != nil {
// Reset the config path for backend loading
configPath = ""
}
var diags tfdiags.Diagnostics
var backendConfig *configs.Backend
if plan == nil {
var configDiags tfdiags.Diagnostics
backendConfig, configDiags = c.loadBackendConfig(configPath)
// Load the backend
var be backend.Enhanced
var beDiags tfdiags.Diagnostics
if planFile == nil {
backendConfig, configDiags := c.loadBackendConfig(configPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
}
// Load the backend
b, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
Plan: plan,
})
be, beDiags = c.Backend(&BackendOpts{
Config: backendConfig,
})
} 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)
if beDiags.HasErrors() {
c.showDiagnostics(diags)
@ -148,11 +153,11 @@ func (c *ApplyCommand) Run(args []string) int {
diags = nil
// Build the operation
opReq := c.Operation()
opReq := c.Operation(be)
opReq.AutoApprove = autoApprove
opReq.Destroy = c.Destroy
opReq.ConfigDir = configPath
opReq.Plan = plan
opReq.PlanFile = planFile
opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypeApply
opReq.AutoApprove = autoApprove
@ -163,7 +168,7 @@ func (c *ApplyCommand) Run(args []string) int {
return 1
}
op, err := c.RunOperation(b, opReq)
op, err := c.RunOperation(be, opReq)
if err != nil {
c.showDiagnostics(err)
return 1
@ -314,26 +319,19 @@ Options:
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 {
return ""
}
ms := state.ModuleByPath(modPath)
ms := state.Module(modPath)
if ms == nil {
return ""
}
outputs := ms.Outputs
outputs := ms.OutputValues
outputBuf := new(bytes.Buffer)
if len(outputs) > 0 {
schemaMap := make(map[string]*config.Output)
if schema != nil {
for _, s := range schema {
schemaMap[s.Name] = s
}
}
if includeHeader {
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
}
@ -350,23 +348,14 @@ func outputsAsString(state *terraform.State, modPath addrs.ModuleInstance, schem
sort.Strings(ks)
for _, k := range ks {
schema, ok := schemaMap[k]
schema, ok := schema[k]
if ok && schema.Sensitive {
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
continue
}
v := outputs[k]
switch typedV := v.Value.(type) {
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")
}
//v := outputs[k]
outputBuf.WriteString("output printer not yet updated to use the same value formatter as 'terraform console'")
}
}

View File

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

View File

@ -19,8 +19,12 @@ import (
"github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
)
@ -285,8 +289,6 @@ func TestApply_defaultState(t *testing.T) {
t.Fatal(err)
}
serial := localState.State().Serial
args := []string{
"-auto-approve",
testFixturePath("apply"),
@ -303,10 +305,6 @@ func TestApply_defaultState(t *testing.T) {
if state == 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) {
@ -499,9 +497,7 @@ func TestApply_plan(t *testing.T) {
defaultInputReader = new(bytes.Buffer)
defaultInputWriter = new(bytes.Buffer)
planPath := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply"),
})
planPath := testPlanFileNoop(t)
statePath := testTempFile(t)
p := testProvider()
@ -536,8 +532,7 @@ func TestApply_plan(t *testing.T) {
}
func TestApply_plan_backup(t *testing.T) {
plan := testPlan(t)
planPath := testPlanFile(t, plan)
planPath := testPlanFileNoop(t)
statePath := 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
err := (&state.LocalState{Path: statePath}).WriteState(plan.State)
err := statemgr.NewFilesystem(statePath).WriteState(states.NewState())
if err != nil {
t.Fatal(err)
}
@ -569,7 +564,7 @@ func TestApply_plan_backup(t *testing.T) {
}
func TestApply_plan_noBackup(t *testing.T) {
planPath := testPlanFile(t, testPlan(t))
planPath := testPlanFileNoop(t)
statePath := testTempFile(t)
p := testProvider()
@ -620,14 +615,12 @@ func TestApply_plan_remoteState(t *testing.T) {
// Create a remote state
state := testState()
conf, srv := testRemoteState(t, state, 200)
backendState, srv := testRemoteState(t, state, 200)
defer srv.Close()
state.Remote = conf
testStateFileRemote(t, backendState)
planPath := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply"),
State: state,
})
_, snap := testModuleWithSnapshot(t, "apply")
planPath := testPlanFile(t, snap, state, &plans.Plan{})
p := testProvider()
ui := new(cli.MockUi)
@ -668,9 +661,7 @@ func TestApply_planWithVarFile(t *testing.T) {
t.Fatalf("err: %s", err)
}
planPath := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply"),
})
planPath := testPlanFileNoop(t)
statePath := testTempFile(t)
cwd, err := os.Getwd()
@ -710,9 +701,7 @@ func TestApply_planWithVarFile(t *testing.T) {
}
func TestApply_planVars(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply"),
})
planPath := testPlanFileNoop(t)
statePath := testTempFile(t)
p := testProvider()
@ -743,9 +732,7 @@ func TestApply_planNoModuleFiles(t *testing.T) {
defer testChdir(t, td)()
p := testProvider()
planFile := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply-plan-no-module"),
})
planFile := testPlanFileNoop(t)
apply := &ApplyCommand{
Meta: Meta{
@ -763,22 +750,20 @@ func TestApply_planNoModuleFiles(t *testing.T) {
}
func TestApply_refresh(t *testing.T) {
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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, originalState)
p := testProvider()
@ -909,22 +894,20 @@ func TestApply_shutdown(t *testing.T) {
}
func TestApply_state(t *testing.T) {
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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, originalState)
p := testProvider()
@ -979,9 +962,6 @@ func TestApply_state(t *testing.T) {
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())
expectedStr := strings.TrimSpace(originalState.String())
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) {
statePath := testTempFile(t)
@ -1285,23 +1209,20 @@ func TestApply_varFileDefaultJSON(t *testing.T) {
}
func TestApply_backup(t *testing.T) {
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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
originalState.Init()
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, originalState)
backupPath := testTempFile(t)

View File

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

View File

@ -15,6 +15,7 @@ import (
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/slowmessage"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli"
"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
// state.Locker.
type Locker interface {
// Lock the provided state, storing the reason string in the LockInfo.
Lock(s state.State, reason string) error
// Lock the provided state manager, storing the reason string in the LockInfo.
Lock(s statemgr.Locker, reason string) error
// Unlock the previously locked state.
// An optional error can be passed in, and will be combined with any error
// from the Unlock operation.
@ -72,7 +73,7 @@ type locker struct {
mu sync.Mutex
ctx context.Context
timeout time.Duration
state state.State
state statemgr.Locker
ui cli.Ui
color *colorstring.Colorize
lockID string
@ -100,7 +101,7 @@ func NewLocker(
// 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
// cancelled.
func (l *locker) Lock(s state.State, reason string) error {
func (l *locker) Lock(s statemgr.Locker, reason string) error {
l.mu.Lock()
defer l.mu.Unlock()
@ -113,7 +114,7 @@ func (l *locker) Lock(s state.State, reason string) error {
lockInfo.Operation = reason
err := slowmessage.Do(LockThreshold, func() error {
id, err := state.LockWithContext(ctx, s, lockInfo)
id, err := statemgr.LockWithContext(ctx, s, lockInfo)
l.lockID = id
return err
}, func() {
@ -165,7 +166,7 @@ func NewNoopLocker() Locker {
return noopLocker{}
}
func (l noopLocker) Lock(state.State, string) error {
func (l noopLocker) Lock(statemgr.Locker, string) error {
return nil
}

View File

@ -19,10 +19,20 @@ import (
"syscall"
"testing"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/configs/configschema"
"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/version"
)
// 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 {
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)
@ -131,48 +147,59 @@ func testModule(t *testing.T, name string) *configs.Config {
t.Fatal(diags.Error())
}
config, diags := loader.LoadConfig(dir)
config, snap, diags := loader.LoadConfigWithSnapshot(dir)
if diags.HasErrors() {
t.Fatal(diags.Error())
}
return config
return config, snap
}
// testPlan returns a non-nil noop plan.
func testPlan(t *testing.T) *terraform.Plan {
func testPlan(t *testing.T) *plans.Plan {
t.Helper()
state := terraform.NewState()
state.RootModule().Outputs["foo"] = &terraform.OutputState{
Type: "string",
Value: "foo",
}
return &terraform.Plan{
Config: testModule(t, "apply"),
State: state,
return &plans.Plan{
Changes: plans.NewChanges(),
}
}
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()
path := testTempFile(t)
f, err := os.Create(path)
if err != nil {
t.Fatalf("err: %s", err)
stateFile := &statefile.File{
Lineage: "command.testPlanFile",
State: state,
TerraformVersion: version.SemVer,
}
defer f.Close()
if err := terraform.WritePlan(plan, f); err != nil {
t.Fatalf("err: %s", err)
path := testTempFile(t)
err := planfile.Create(path, configSnap, stateFile, plan)
if err != nil {
t.Fatalf("failed to create temporary plan file: %s", err)
}
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 {
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.
func testState() *terraform.State {
state := &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",
},
},
},
Outputs: map[string]*terraform.OutputState{},
func testState() *states.State {
return states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
state.Init()
return state
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
})
}
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()
path := testTempFile(t)
f, err := os.Create(path)
if err != nil {
t.Fatalf("err: %s", err)
t.Fatalf("failed to create temporary state file %s: %s", path, err)
}
defer f.Close()
if err := terraform.WriteState(s, f); err != nil {
t.Fatalf("err: %s", err)
err = writeStateForTesting(s, f)
if err != nil {
t.Fatalf("failed to write state to temporary file %s: %s", path, err)
}
return path
@ -272,7 +369,7 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string {
}
// 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()
f, err := os.Open(path)
@ -281,12 +378,34 @@ func testStateRead(t *testing.T, path string) *terraform.State {
}
defer f.Close()
newState, err := terraform.ReadState(f)
sf, err := statefile.Read(f)
if err != nil {
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
@ -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
// 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()
var b64md5 string
@ -591,25 +714,32 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote
resp.Write(buf.Bytes())
}
retState := terraform.NewState()
srv := httptest.NewServer(http.HandlerFunc(cb))
remote := &terraform.RemoteState{
Type: "http",
Config: map[string]string{"address": srv.URL},
b := &terraform.BackendState{
Type: "http",
}
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 {
// Set the remote data
s.Remote = remote
enc := json.NewEncoder(buf)
if err := enc.Encode(s); err != nil {
t.Fatalf("err: %v", err)
err := statefile.Write(&statefile.File{State: s}, buf)
if err != nil {
t.Fatalf("failed to write initial state: %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.

View File

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

View File

@ -6,9 +6,12 @@ import (
"sort"
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
"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
@ -59,100 +62,63 @@ type PlanStats struct {
}
// NewPlan produces a display-oriented Plan from a terraform.Plan.
func NewPlan(plan *terraform.Plan) *Plan {
func NewPlan(changes *plans.Changes) *Plan {
ret := &Plan{}
if plan == nil || plan.Diff == nil || plan.Diff.Empty() {
if changes == nil {
// Nothing to do!
return ret
}
for _, m := range plan.Diff.Modules {
var modulePath []string
if !m.IsRoot() {
// trim off the leading "root" path segment, since it's implied
// when we use a path in a resource address.
modulePath = m.Path[1:]
for _, rc := range changes.Resources {
addr := rc.Addr
dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
// We create "delete" 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 && rc.Action == plans.Delete {
continue
}
for k, r := range m.Resources {
if r.Empty() {
continue
}
// For now we'll shim this to work with our old types.
// TODO: Update for the new plan types, ideally also switching over to
// a structural diff renderer instead of a flat renderer.
did := &InstanceDiff{
Addr: terraform.NewLegacyResourceInstanceAddress(addr),
}
addr, err := terraform.ParseResourceAddressForInstanceDiff(modulePath, k)
if err != nil {
// should never happen; indicates invalid diff
panic("invalid resource address in diff")
}
dataSource := addr.Mode == config.DataResourceMode
// 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.
switch rc.Action {
case plans.Create:
if dataSource {
// 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
// instead.
did.Action = terraform.DiffRefresh
} else {
did.Action = terraform.DiffCreate
}
ret.Resources = append(ret.Resources, did)
if did.Action == terraform.DiffDestroy {
// Don't show any outputs for destroy actions
continue
}
for k, a := range r.Attributes {
var action terraform.DiffChangeType
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
}
})
case plans.Read:
did.Action = terraform.DiffRefresh
case plans.Delete:
did.Action = terraform.DiffDestroy
case plans.Replace:
did.Action = terraform.DiffDestroyCreate
case plans.Update:
did.Action = terraform.DiffUpdate
default:
panic(fmt.Sprintf("unexpected change action %s", rc.Action))
}
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.

View File

@ -6,14 +6,16 @@ import (
"sort"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
)
// StateOpts are the options for formatting a state.
type StateOpts struct {
// State is the state to format. This is required.
State *terraform.State
State *states.State
// Color is the colorizer. This is optional.
Color *colorstring.Colorize
@ -34,7 +36,10 @@ func State(opts *StateOpts) string {
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]")
// Format all the modules
@ -76,6 +81,7 @@ func State(opts *StateOpts) string {
}
return opts.Color.Color(strings.TrimSpace(buf.String()))
*/
}
func formatStateModuleExpand(

View File

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

View File

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

View File

@ -10,9 +10,15 @@ import (
"time"
"unicode"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/plans"
"github.com/mitchellh/cli"
"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
@ -31,6 +37,8 @@ type UiHook struct {
ui cli.Ui
}
var _ terraform.Hook = (*UiHook)(nil)
// uiResourceState tracks the state of a single resource
type uiResourceState struct {
Name string
@ -53,37 +61,21 @@ const (
uiResourceDestroy
)
func (h *UiHook) PreApply(
n *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff) (terraform.HookAction, error) {
func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
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
switch op {
case uiResourceModify:
operation = "Modifying..."
case uiResourceDestroy:
var op uiResourceOp
switch action {
case plans.Delete:
operation = "Destroying..."
case uiResourceCreate:
op = uiResourceDestroy
case plans.Create:
operation = "Creating..."
case uiResourceUnknown:
return terraform.HookActionContinue, nil
op = uiResourceCreate
default:
operation = "Modifying..."
op = uiResourceModify
}
attrBuf := new(bytes.Buffer)
@ -92,7 +84,11 @@ func (h *UiHook) PreApply(
// determine the longest key so that we can align them all.
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))
for key, _ := range dAttrs {
// Skip the ID since we do that specially
@ -109,7 +105,7 @@ func (h *UiHook) PreApply(
// Go through and output each attribute
for _, attrK := range keys {
attrDiff, _ := d.GetAttribute(attrK)
attrDiff := dAttrs[attrK]
v := attrDiff.New
u := attrDiff.Old
@ -136,18 +132,16 @@ func (h *UiHook) PreApply(
}
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(
"[reset][bold]%s: %s%s[reset]%s",
addr,
operation,
stateIdSuffix,
attrString)))
attrString,
)))
id := addr.String()
uiState := uiResourceState{
Name: id,
ResourceId: stateId,
@ -205,13 +199,9 @@ func (h *UiHook) stillApplying(state uiResourceState) {
}
}
func (h *UiHook) PostApply(
n *terraform.InstanceInfo,
s *terraform.InstanceState,
applyerr error) (terraform.HookAction, error) {
func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, applyerr error) (terraform.HookAction, error) {
id := n.HumanId()
addr := n.ResourceAddress()
id := addr.String()
h.l.Lock()
state := h.resources[id]
@ -223,9 +213,6 @@ func (h *UiHook) PostApply(
h.l.Unlock()
var stateIdSuffix string
if s != nil && s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
var msg string
switch state.Op {
@ -253,31 +240,23 @@ func (h *UiHook) PostApply(
return terraform.HookActionContinue, nil
}
func (h *UiHook) PreDiff(
n *terraform.InstanceInfo,
s *terraform.InstanceState) (terraform.HookAction, error) {
func (h *UiHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (terraform.HookAction, error) {
return terraform.HookActionContinue, nil
}
func (h *UiHook) PreProvision(
n *terraform.InstanceInfo,
provId string) (terraform.HookAction, error) {
addr := n.ResourceAddress()
func (h *UiHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (terraform.HookAction, error) {
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Provisioning with '%s'...[reset]",
addr, provId)))
addr, typeName,
)))
return terraform.HookActionContinue, nil
}
func (h *UiHook) ProvisionOutput(
n *terraform.InstanceInfo,
provId string,
msg string) {
addr := n.ResourceAddress()
func (h *UiHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, msg string) {
var buf bytes.Buffer
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.Split(scanLines)
for s.Scan() {
@ -290,19 +269,10 @@ func (h *UiHook) ProvisionOutput(
h.ui.Output(strings.TrimSpace(buf.String()))
}
func (h *UiHook) PreRefresh(
n *terraform.InstanceInfo,
s *terraform.InstanceState) (terraform.HookAction, error) {
func (h *UiHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (terraform.HookAction, error) {
h.once.Do(h.init)
addr := n.ResourceAddress()
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(
"[reset][bold]%s: Refreshing state...%s",
@ -310,30 +280,26 @@ func (h *UiHook) PreRefresh(
return terraform.HookActionContinue, nil
}
func (h *UiHook) PreImportState(
n *terraform.InstanceInfo,
id string) (terraform.HookAction, error) {
func (h *UiHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (terraform.HookAction, error) {
h.once.Do(h.init)
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Importing from ID %q...",
addr, id)))
addr, importID,
)))
return terraform.HookActionContinue, nil
}
func (h *UiHook) PostImportState(
n *terraform.InstanceInfo,
s []*terraform.InstanceState) (terraform.HookAction, error) {
func (h *UiHook) PostImportState(addr addrs.AbsResourceInstance, imported []*states.ImportedObject) (terraform.HookAction, error) {
h.once.Do(h.init)
addr := n.ResourceAddress()
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold][green]%s: Import complete!", addr)))
for _, s := range s {
for _, s := range imported {
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][green] Imported %s (ID: %s)",
s.Ephemeral.Type, s.ID)))
"[reset][green] Imported %s",
s.ResourceType,
)))
}
return terraform.HookActionContinue, nil

View File

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

View File

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

View File

@ -8,17 +8,19 @@ import (
"strings"
"github.com/hashicorp/hcl2/hcl"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
)
// 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
// on a previous run) we'll use the current state as a potential source
// of provider dependencies.
if back != nil {
sMgr, err := back.State(c.Workspace())
sMgr, err := back.StateMgr(c.Workspace())
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
return 1
@ -391,17 +393,12 @@ func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configsc
// Load the complete module tree, and fetch any missing providers.
// 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)
if diags.HasErrors() {
return diags
}
if err := terraform.CheckStateVersion(state, false); err != nil {
diags = diags.Append(err)
return diags
}
var available discovery.PluginMetaSet
if upgrade {
// 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())
}
s := testStateRead(t, filepath.Join(
DefaultDataDir, DefaultStateFilename))
s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
if !s.Backend.Empty() {
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
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 {
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
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 {
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
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 {
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
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 {
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 {
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 {
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
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 {
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 {
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 {
t.Fatal("state.Backend.Hash was not updated")

View File

@ -318,13 +318,12 @@ const (
// context with the settings from this Meta.
func (m *Meta) contextOpts() *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.Targets = m.targets
opts.UIInput = m.UIInput()
opts.Parallelism = m.parallelism
opts.Shadow = m.shadow
// If testingOverrides are set, we'll skip the plugin discovery process
// 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/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init"
backendlocal "github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"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.
@ -38,10 +40,6 @@ type BackendOpts struct {
// arguments in Config.
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
// false, then any configuration that requires configuration will show
// an error asking the user to reinitialize.
@ -78,17 +76,9 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
// local operation.
var b backend.Backend
if !opts.ForceLocal {
// If we have a plan then, we get the the backend from there. Otherwise,
// the backend comes from the configuration.
if opts.Plan != nil {
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)
}
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.backendFromConfig(opts)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
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)
}
// Setup the CLI opts we pass into backends that support it.
cliOpts := &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,
}
// 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
// Setup the CLI opts we pass into backends that support it
cliOpts := m.backendCLIOpts()
cliOpts.Validation = true
// If the backend supports CLI initialization, do it.
if cli, ok := b.(backend.CLI); ok {
@ -151,6 +127,73 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
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
// for some checks that require a remote backend.
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
// to modify fields of the operation such as Sequence to specify what will
// 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{
PlanOutBackend: m.backendState,
Parallelism: m.parallelism,
PlanOutBackend: planOutBackend,
Targets: m.targets,
UIIn: m.UIInput(),
UIOut: m.Ui,
Variables: m.variables,
Workspace: m.Workspace(),
Workspace: workspace,
LockState: m.stateLock,
StateLockTimeout: m.stateLockTimeout,
}
@ -242,9 +294,12 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
// backendFromConfig returns the initialized (not configured) backend
// directly from the config/state..
//
// This function handles any edge cases around backend config loading. For
// example: legacy remote state, new config changes, backend type changes,
// etc.
// This function handles various edge cases around backend config loading. For
// example: new config changes, backend type changes, 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
// 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
// of state settings between: configuring new backends, saved (previously-
// configured) backends, and legacy remote state.
if !s.Remote.Empty() {
// Legacy remote state is no longer supported. User must first
// 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 {
// 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
// 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 {
initReason := fmt.Sprintf(
"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)
// 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.
case c != nil && s.Remote.Empty() && s.Backend.Empty():
case c != nil && s.Backend.Empty():
if !opts.Init {
initReason := fmt.Sprintf(
"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)
// 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
// a previously configured remote backend.
if !s.Backend.Empty() {
@ -369,237 +412,18 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
s.Backend.Hash, cHash)
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:
// 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(
"Unhandled backend configuration state. This is a bug. Please\n"+
"report this error with the following information.\n\n"+
"Config Nil: %v\n"+
"Saved Backend Empty: %v\n"+
"Legacy Remote Empty: %v\n",
c == nil, s.Backend.Empty(), s.Remote.Empty(),
"Saved Backend Empty: %v\n",
c == nil, s.Backend.Empty(),
))
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
//
@ -618,7 +442,7 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, tfdiags.Diag
//-------------------------------------------------------------------
// 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()
// 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
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
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
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
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.
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
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.
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
b, configVal, diags := m.backendInitFromConfig(c)
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
}
workspaces, err := localB.States()
workspace := m.Workspace()
localState, err := localB.StateMgr(workspace)
if err != nil {
diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
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.
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 {
// Notify the user
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
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
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.
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
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.
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
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")

View File

@ -11,6 +11,9 @@ import (
"strconv"
"strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"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
// from multi-state to single-state for example, we need to handle that.
var oneSingle, twoSingle bool
oneStates, err := opts.One.States()
if err == backend.ErrNamedStatesNotSupported {
oneStates, err := opts.One.Workspaces()
if err == backend.ErrWorkspacesNotSupported {
oneSingle = true
err = nil
}
@ -53,8 +56,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
errMigrateLoadStates), opts.OneType, err)
}
_, err = opts.Two.States()
if err == backend.ErrNamedStatesNotSupported {
_, err = opts.Two.Workspaces()
if err == backend.ErrWorkspacesNotSupported {
twoSingle = true
err = nil
}
@ -144,7 +147,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
}
// Read all the states
oneStates, err := opts.One.States()
oneStates, err := opts.One.Workspaces()
if err != nil {
return fmt.Errorf(strings.TrimSpace(
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.
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 {
return fmt.Errorf(strings.TrimSpace(
errMigrateSingleLoadDefault), opts.OneType, err)
@ -270,47 +273,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
errMigrateSingleLoadDefault), opts.OneType, err)
}
// Do not migrate workspaces without state.
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
}()
}
stateTwo, err := opts.Two.StateMgr(opts.twoEnv)
if err != nil {
return fmt.Errorf(strings.TrimSpace(
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
if one.Equal(two) {
// Equal isn't identical; it doesn't check lineage.
if one != nil && two != nil && one.Lineage == two.Lineage {
return nil
sm1, _ := stateOne.(statemgr.PersistentMeta)
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()
}
// 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)
switch {
// No migration necessary
@ -453,14 +414,9 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
defer os.RemoveAll(td)
// Helper to write the state
saveHelper := func(n, path string, s *terraform.State) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return terraform.WriteState(s, f)
saveHelper := func(n, path string, s *states.State) error {
mgr := statemgr.NewFilesystem(path)
return mgr.WriteState(s)
}
// Write the states

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,11 @@ import (
"path/filepath"
"strconv"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
@ -129,51 +130,23 @@ func (m *Meta) Config(path string) (*config.Config, error) {
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 error is nil and the plan is nil, then the path didn't look like
// a plan.
// If the return value and error are both nil, the given path exists but seems
// to be a configuration directory instead.
//
// Error will be non-nil if path looks like a plan and loading the plan
// failed.
func (m *Meta) Plan(path string) (*terraform.Plan, error) {
// Open the path no matter if its a directory or file
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)
// Error will be non-nil if path refers to something which looks like a plan
// file and loading the file fails.
func (m *Meta) PlanFile(path string) (*planfile.Reader, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
// We do a validation here that seems odd but if any plan is given,
// we must not have set any extra variables. The plan itself contains
// the variables and those aren't overwritten.
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.")
if fi.IsDir() {
// Looks like a configuration directory.
return nil, nil
}
return p, nil
return planfile.Open(path)
}

View File

@ -2,14 +2,15 @@ package command
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"sort"
"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"
)
@ -63,7 +64,7 @@ func (c *OutputCommand) Run(args []string) int {
env := c.Workspace()
// Get the state
stateStore, err := b.State(env)
stateStore, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -74,13 +75,15 @@ func (c *OutputCommand) Run(args []string) int {
return 1
}
// This command uses a legacy shorthand syntax for the module path that
// can't deal with keyed instances, so we'll just shim it for now and
// make the breaking change for this interface later.
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
moduleAddr, addrDiags := addrs.ParseModuleInstanceStr(module)
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
state := stateStore.State()
mod := state.ModuleByPath(modPath)
mod := state.Module(moduleAddr)
if mod == nil {
c.Ui.Error(fmt.Sprintf(
"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
}
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(
"The state file either has no outputs defined, or all the defined\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 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 {
return 1
}
@ -109,12 +122,12 @@ func (c *OutputCommand) Run(args []string) int {
c.Ui.Output(string(jsonOutputs))
return 0
} else {
c.Ui.Output(outputsAsString(state, modPath, nil, false))
c.Ui.Output(outputsAsString(state, moduleAddr, nil, false))
return 0
}
}
v, ok := mod.Outputs[name]
os, ok := mod.OutputValues[name]
if !ok {
c.Ui.Error(fmt.Sprintf(
"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."))
return 1
}
v := os.Value
if jsonOutput {
jsonOutputs, err := json.MarshalIndent(v, "", " ")
jsonOutput, err := ctyjson.Marshal(v, v.Type())
if err != nil {
return 1
}
c.Ui.Output(string(jsonOutputs))
c.Ui.Output(string(jsonOutput))
} else {
switch output := v.Value.(type) {
case string:
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
}
c.Ui.Error("TODO: update output command to use the same value renderer as the console")
return 1
}
return 0

View File

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

View File

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

View File

@ -11,7 +11,9 @@ import (
"testing"
"time"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -83,9 +85,7 @@ func TestPlan_plan(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
planPath := testPlanFile(t, &terraform.Plan{
Config: testModule(t, "apply"),
})
planPath := testPlanFileNoop(t)
p := testProvider()
ui := new(cli.MockUi)
@ -107,22 +107,20 @@ func TestPlan_plan(t *testing.T) {
}
func TestPlan_destroy(t *testing.T) {
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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
outPath := testTempFile(t)
statePath := testStateFile(t, originalState)
@ -232,21 +230,20 @@ func TestPlan_outPath(t *testing.T) {
}
func TestPlan_outPathNoChange(t *testing.T) {
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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, originalState)
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) {
tmp, cwd := testCwd(t)
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) {
// This is triggered by not asking for input so we have to set this to false
test = false

View File

@ -13,7 +13,6 @@ import (
"strings"
plugin "github.com/hashicorp/go-plugin"
terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/terraform"
@ -280,9 +279,11 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory {
return map[string]terraform.ResourceProviderFactory{
"terraform": func() (terraform.ResourceProvider, error) {
return terraformProvider.Provider(), nil
},
// FIXME: Re-enable this once the internal provider system is updated
// 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
env := c.Workspace()
state, err := b.State(env)
state, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1

View File

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

View File

@ -2,18 +2,19 @@ package command
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"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/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
)
func TestRefresh(t *testing.T) {
@ -185,263 +186,162 @@ func TestRefresh_cwd(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
// default filename.
statePath := testStateFile(t, originalState)
// Write the state file in a temporary directory with the
// default filename.
statePath := testStateFile(t, originalState)
localState := &state.LocalState{Path: statePath}
if err := localState.RefreshState(); err != nil {
t.Fatal(err)
}
s := localState.State()
if s == nil {
t.Fatal("empty test state")
}
serial := s.Serial
localState := &state.LocalState{Path: statePath}
if err := localState.RefreshState(); err != nil {
t.Fatal(err)
}
s := localState.State()
if s == nil {
t.Fatal("empty test state")
}
serial := s.Serial
// Change to that directory
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(filepath.Dir(statePath)); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
// Change to that directory
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(filepath.Dir(statePath)); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("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())
}
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")
}
if !p.RefreshCalled {
t.Fatal("refresh should be called")
}
newState := testStateRead(t, statePath)
newState := testStateRead(t, statePath)
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Logf("expected:\n%#v", expected)
t.Fatalf("bad:\n%#v", actual)
}
actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Logf("expected:\n%#v", expected)
t.Fatalf("bad:\n%#v", actual)
}
if newState.Serial <= serial {
t.Fatalf("serial not incremented during refresh. previous:%d, current:%d", serial, newState.Serial)
}
backupState := testStateRead(t, statePath+DefaultBackupExtension)
backupState := testStateRead(t, statePath+DefaultBackupExtension)
actual = backupState.RootModule().Resources["test_instance.foo"].Primary
expected = originalState.RootModule().Resources["test_instance.foo"].Primary
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)
}
actual = backupState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
expected = originalState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
*/
}
func TestRefresh_outPath(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
t.Fatal("not yet updated for new provider types")
/*
state := testState()
statePath := testStateFile(t, state)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
args := []string{
"-state", statePath,
"-state-out", outPath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
args := []string{
"-state", statePath,
"-state-out", outPath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
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)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
f, err = os.Open(outPath + DefaultBackupExtension)
if err != nil {
t.Fatalf("err: %s", err)
}
f, err = os.Open(outPath + DefaultBackupExtension)
if err != nil {
t.Fatalf("err: %s", err)
}
backupState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
backupState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
*/
}
func TestRefresh_var(t *testing.T) {
@ -582,175 +482,181 @@ func TestRefresh_varsUnset(t *testing.T) {
}
func TestRefresh_backup(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
t.Fatal("not yet updated for new provider types")
/*
state := testState()
statePath := testStateFile(t, state)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
// Backup path
backupf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
backupPath := backupf.Name()
backupf.Close()
os.Remove(backupPath)
// Backup path
backupf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
backupPath := backupf.Name()
backupf.Close()
os.Remove(backupPath)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
args := []string{
"-state", statePath,
"-state-out", outPath,
"-backup", backupPath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
args := []string{
"-state", statePath,
"-state-out", outPath,
"-backup", backupPath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
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)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
f, err = os.Open(backupPath)
if err != nil {
t.Fatalf("err: %s", err)
}
f, err = os.Open(backupPath)
if err != nil {
t.Fatalf("err: %s", err)
}
backupState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
backupState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
actualStr := strings.TrimSpace(backupState.String())
expectedStr := strings.TrimSpace(state.String())
if actualStr != expectedStr {
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
}
*/
}
func TestRefresh_disableBackup(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
t.Fatal("not yet updated for new provider types")
/*
state := testState()
statePath := testStateFile(t, state)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
// Output path
outf, err := ioutil.TempFile(testingDir, "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
outPath := outf.Name()
outf.Close()
os.Remove(outPath)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
p.RefreshFn = nil
p.RefreshReturn = newInstanceState("yes")
args := []string{
"-state", statePath,
"-state-out", outPath,
"-backup", "-",
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
args := []string{
"-state", statePath,
"-state-out", outPath,
"-backup", "-",
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
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)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
if !reflect.DeepEqual(newState, state) {
t.Fatalf("bad: %#v", newState)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
f, err = os.Open(outPath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err = terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
actual := newState.RootModule().Resources["test_instance.foo"].Primary
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
// Ensure there is no backup
_, err = os.Stat(outPath + DefaultBackupExtension)
if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist")
}
_, err = os.Stat("-")
if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist")
}
// Ensure there is no backup
_, err = os.Stat(outPath + DefaultBackupExtension)
if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist")
}
_, err = os.Stat("-")
if err == nil || !os.IsNotExist(err) {
t.Fatalf("backup should not exist")
}
*/
}
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
// terraform.State, all fields must be initialized (duplicating the
// InstanceState.init() method)
func newInstanceState(id string) *terraform.InstanceState {
return &terraform.InstanceState{
ID: id,
Attributes: make(map[string]string),
Ephemeral: terraform.EphemeralState{
ConnInfo: make(map[string]string),
},
Meta: make(map[string]interface{}),
// newInstanceState creates a new states.ResourceInstanceObjectSrc with the
// given value for its single id attribute. It is named newInstanceState for
// historical reasons, because it was originally written for the poorly-named
// terraform.InstanceState type.
func newInstanceState(id string) *states.ResourceInstanceObjectSrc {
attrs := map[string]interface{}{
"id": id,
}
attrsJSON, err := json.Marshal(attrs)
if err != nil {
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"
"strings"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statefile"
"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
@ -42,31 +46,30 @@ func (c *ShowCommand) Run(args []string) int {
var planErr, stateErr error
var path string
var plan *terraform.Plan
var state *terraform.State
var plan *plans.Plan
var state *states.State
if len(args) > 0 {
path = args[0]
f, err := os.Open(path)
pr, err := planfile.Open(path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
return 1
}
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))
f, err := os.Open(path)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
return 1
}
defer f.Close()
plan = nil
planErr = err
}
if plan == nil {
state, err = terraform.ReadState(f)
var stateFile *statefile.File
stateFile, err = statefile.Read(f)
if err != nil {
stateErr = err
} else {
state = stateFile.State
}
} else {
plan, err = pr.ReadPlan()
if err != nil {
planErr = err
}
}
} else {
@ -80,7 +83,7 @@ func (c *ShowCommand) Run(args []string) int {
env := c.Workspace()
// Get the state
stateStore, err := b.State(env)
stateStore, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -110,7 +113,7 @@ func (c *ShowCommand) Run(args []string) int {
}
if plan != nil {
dispPlan := format.NewPlan(plan)
dispPlan := format.NewPlan(plan.Changes)
c.Ui.Output(dispPlan.Format(c.Colorize()))
return 0
}

View File

@ -2,12 +2,8 @@ package command
import (
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -68,9 +64,7 @@ func TestShow_noArgsNoState(t *testing.T) {
}
func TestShow_plan(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{
Config: configs.NewEmptyConfig(),
})
planPath := testPlanFileNoop(t)
ui := new(cli.MockUi)
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) {
originalState := testState()
statePath := testStateFile(t, originalState)

View File

@ -4,7 +4,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -23,7 +22,7 @@ func (c *StateListCommand) Run(args []string) int {
cmdFlags := c.Meta.flagSet("state list")
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 {
return cli.RunResultHelp
}
@ -38,7 +37,7 @@ func (c *StateListCommand) Run(args []string) int {
env := c.Workspace()
// Get the state
state, err := b.State(env)
state, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -55,7 +54,11 @@ func (c *StateListCommand) Run(args []string) int {
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...)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
@ -68,7 +71,7 @@ func (c *StateListCommand) Run(args []string) int {
c.Ui.Output(result.Address)
}
}
}
}*/
return 0
}

View File

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

View File

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

View File

@ -1,47 +1,46 @@
package command
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform"
"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) {
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()
@ -84,37 +83,32 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
backupPath := filepath.Join(td, "backup")
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
// init our backend
@ -162,37 +156,32 @@ func TestStateMv_backupExplicit(t *testing.T) {
defer os.RemoveAll(td)
backupPath := filepath.Join(td, "backup")
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()
@ -224,26 +213,20 @@ func TestStateMv_backupExplicit(t *testing.T) {
}
func TestStateMv_stateOutNew(t *testing.T) {
state := &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",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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)
stateOutPath := statePath + ".out"
@ -281,44 +264,36 @@ func TestStateMv_stateOutNew(t *testing.T) {
}
func TestStateMv_stateOutExisting(t *testing.T) {
stateSrc := &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",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
stateSrc := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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, stateSrc)
stateDst := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.qux": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
},
},
},
stateDst := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "qux",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
stateOutPath := testStateFile(t, stateDst)
p := testProvider()
@ -382,48 +357,44 @@ func TestStateMv_noState(t *testing.T) {
}
func TestStateMv_stateOutNew_count(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.0": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","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: "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)
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
// indexes in the new location to change.
func TestStateMv_stateOutNew_largeCount(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.0": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "foo0",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
"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",
},
},
},
state := states.BuildState(func(s *states.SyncState) {
// test_instance.foo has 11 instances, all the same except for their ids
for i := 0; i < 11; i++ {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(fmt.Sprintf(`{"id":"foo%d","foo":"value","bar":"value"}`, i)),
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)
stateOutPath := statePath + ".out"
@ -641,51 +500,32 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) {
}
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
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,
},
&terraform.ModuleState{
Path: []string{"root", "foo"},
Resources: map[string]*terraform.ResourceState{},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
s.SetResourceInstanceCurrent(
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,
},
&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",
},
},
},
},
},
},
}
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state)
stateOutPath := statePath + ".out"

View File

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

View File

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

View File

@ -1,12 +1,8 @@
package command
import (
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -35,80 +31,86 @@ func (c *StatePushCommand) Run(args []string) int {
return 1
}
// 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])
c.Ui.Error("state push not yet updated for new state types")
return 1
/*
// 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 {
c.Ui.Error(err.Error())
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
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 {
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))
// Load the backend
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}
age, err := dstState.CompareAges(sourceState)
// Get the state
env := c.Workspace()
state, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(err.Error())
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
if age == terraform.StateAgeReceiverNewer {
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
if err := state.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
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
}
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
}
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
}

View File

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

View File

@ -47,10 +47,15 @@ func (c *StateRmCommand) Run(args []string) int {
return 1
}
if err := stateReal.Remove(args...); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRm, err))
return 1
}
c.Ui.Error("state rm not yet updated for new state types")
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)))

View File

@ -6,43 +6,41 @@ import (
"strings"
"testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform"
"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) {
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()
@ -76,37 +74,32 @@ func TestStateRm(t *testing.T) {
}
func TestStateRmNoArgs(t *testing.T) {
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()
@ -138,37 +131,32 @@ func TestStateRm_backupExplicit(t *testing.T) {
defer os.RemoveAll(td)
backupPath := filepath.Join(td, "backup")
state := &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",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()

View File

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

View File

@ -4,31 +4,27 @@ import (
"strings"
"testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
)
func TestStateShow(t *testing.T) {
state := &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",
Attributes: map[string]string{
"foo": "value",
"bar": "value",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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)
p := testProvider()
@ -57,36 +53,32 @@ func TestStateShow(t *testing.T) {
}
func TestStateShow_multi(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo.0": &terraform.ResourceState{
Type: "test_instance",
Primary: &terraform.InstanceState{
ID: "bar",
Attributes: map[string]string{
"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",
},
},
},
},
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.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),
)
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)
p := testProvider()
@ -127,8 +119,7 @@ func TestStateShow_noState(t *testing.T) {
}
func TestStateShow_emptyState(t *testing.T) {
state := terraform.NewState()
state := states.NewState()
statePath := testStateFile(t, state)
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 = `
id = bar
bar = value

View File

@ -3,12 +3,13 @@ package command
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/addrs"
"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
@ -38,6 +39,8 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
var diags tfdiags.Diagnostics
// Require the one argument for the resource to taint
args = cmdFlags.Args()
if len(args) != 1 {
@ -46,34 +49,34 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
name := args[0]
if module == "" {
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))
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\".")
return 1
}
if !rsk.Mode.Taintable() {
c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name))
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
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
}
// Load the backend
b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
c.showDiagnostics(diags)
return 1
}
// Get the state
env := c.Workspace()
st, err := b.State(env)
st, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -97,63 +100,60 @@ func (c *TaintCommand) Run(args []string) int {
s := st.State()
if s.Empty() {
if allowMissing {
return c.allowMissingExit(name, module)
return c.allowMissingExit(addr)
}
c.Ui.Error(fmt.Sprintf(
"The state is empty. The most common reason for this is that\n" +
"an invalid state file path was given or Terraform has never\n " +
"been run for this infrastructure. Infrastructure must exist\n" +
"for it to be tainted."))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
"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.",
))
c.showDiagnostics(diags)
return 1
}
// Get the ModuleState where we will taint. This is provided in a legacy
// string form that doesn't support module instance keys, so we'll shim
// it here.
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
mod := s.ModuleByPath(modPath)
if mod == nil {
state := s.SyncWrapper()
// Get the resource and instance we're going to taint
rs := state.Resource(addr.ContainingResource())
is := state.ResourceInstance(addr)
if is == nil {
if allowMissing {
return c.allowMissingExit(name, module)
return c.allowMissingExit(addr)
}
c.Ui.Error(fmt.Sprintf(
"The module %s could not be found. There is nothing to taint.",
module))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"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
}
// If there are no resources in this module, it is an error
if len(mod.Resources) == 0 {
if allowMissing {
return c.allowMissingExit(name, module)
obj := is.Current
if obj == nil {
if len(is.Deposed) != 0 {
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.Ui.Error(fmt.Sprintf(
"The module %s has no resources. There is nothing to taint.",
module))
c.showDiagnostics(diags)
return 1
}
// Get the resource we're looking for
rs, ok := mod.Resources[name]
if !ok {
if allowMissing {
return c.allowMissingExit(name, module)
}
obj.Status = states.ObjectTainted
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
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 {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
@ -163,24 +163,28 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
c.Ui.Output(fmt.Sprintf(
"The resource %s in the module %s has been marked as tainted!",
name, module))
c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr))
return 0
}
func (c *TaintCommand) Help() string {
helpText := `
Usage: terraform taint [options] name
Usage: terraform taint [options] <address>
Manually mark a resource as tainted, forcing a destroy and recreate
on the next plan/apply.
This will not modify your infrastructure. This command changes your
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
its own will not modify infrastructure. This command can be undone by
reverting the state backup file that is created.
apply that resource will be destroyed and recreated. This command on
its own will not modify infrastructure. This command can be undone
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:
@ -195,10 +199,6 @@ Options:
-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.
-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"
}
func (c *TaintCommand) allowMissingExit(name, module string) int {
c.Ui.Output(fmt.Sprintf(
"The resource %s in the module %s was not found, but\n"+
"-allow-missing is set, so we're exiting successfully.",
name, module))
func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
c.showDiagnostics(tfdiags.Sourceless(
tfdiags.Warning,
"No such resource instance",
"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
))
return 0
}

View File

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

View File

@ -4,7 +4,8 @@ import (
"fmt"
"strings"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
@ -67,21 +68,13 @@ func (c *UnlockCommand) Run(args []string) int {
}
env := c.Workspace()
st, err := b.State(env)
st, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
isLocal := false
switch s := st.(type) {
case *state.BackupState:
if _, ok := s.Real.(*state.LocalState); ok {
isLocal = true
}
case *state.LocalState:
isLocal = true
}
_, isLocal := st.(*statemgr.Filesystem)
if !force {
// 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 {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(testState(), f)
err = terraform.WriteState(terraform.NewState(), f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)

View File

@ -3,9 +3,12 @@ package command
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate"
)
@ -37,6 +40,8 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
var diags tfdiags.Diagnostics
// Require the one argument for the resource to untaint
args = cmdFlags.Args()
if len(args) != 1 {
@ -45,23 +50,29 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
name := args[0]
if module == "" {
module = "root"
} else {
module = "root." + 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\".")
return 1
}
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
c.showDiagnostics(diags)
return 1
}
// Get the state
env := c.Workspace()
st, err := b.State(env)
workspace := c.Workspace()
st, err := b.StateMgr(workspace)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -85,63 +96,69 @@ func (c *UntaintCommand) Run(args []string) int {
s := st.State()
if s.Empty() {
if allowMissing {
return c.allowMissingExit(name, module)
return c.allowMissingExit(addr)
}
c.Ui.Error(fmt.Sprintf(
"The state is empty. The most common reason for this is that\n" +
"an invalid state file path was given or Terraform has never\n " +
"been run for this infrastructure. Infrastructure must exist\n" +
"for it to be untainted."))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
"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.",
))
c.showDiagnostics(diags)
return 1
}
// Get the ModuleState where we will untaint. This is provided in a legacy
// string form that doesn't support module instance keys, so we'll shim
// it here.
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
mod := s.ModuleByPath(modPath)
if mod == nil {
state := s.SyncWrapper()
// Get the resource and instance we're going to taint
rs := state.Resource(addr.ContainingResource())
is := state.ResourceInstance(addr)
if is == nil {
if allowMissing {
return c.allowMissingExit(name, module)
return c.allowMissingExit(addr)
}
c.Ui.Error(fmt.Sprintf(
"The module %s could not be found. There is nothing to untaint.",
module))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"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
}
// If there are no resources in this module, it is an error
if len(mod.Resources) == 0 {
if allowMissing {
return c.allowMissingExit(name, module)
obj := is.Current
if obj == nil {
if len(is.Deposed) != 0 {
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.Ui.Error(fmt.Sprintf(
"The module %s has no resources. There is nothing to untaint.",
module))
c.showDiagnostics(diags)
return 1
}
// Get the resource we're looking for
rs, ok := mod.Resources[name]
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))
if obj.Status != states.ObjectTainted {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Resource instance is not tainted",
fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr),
))
c.showDiagnostics(diags)
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 {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
@ -151,9 +168,7 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
c.Ui.Output(fmt.Sprintf(
"The resource %s in the module %s has been successfully untainted!",
name, module))
c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr))
return 0
}
@ -203,10 +218,11 @@ func (c *UntaintCommand) Synopsis() string {
return "Manually unmark a resource as tainted"
}
func (c *UntaintCommand) allowMissingExit(name, module string) int {
c.Ui.Output(fmt.Sprintf(
"The resource %s in the module %s was not found, but\n"+
"-allow-missing is set, so we're exiting successfully.",
name, module))
func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
c.showDiagnostics(tfdiags.Sourceless(
tfdiags.Warning,
"No such resource instance",
"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
))
return 0
}

View File

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

View File

@ -7,11 +7,14 @@ import (
"strings"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/backend/remote-state/inmem"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -227,24 +230,22 @@ func TestWorkspace_createWithState(t *testing.T) {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}
// create a non-empty 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 := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"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 {
t.Fatal(err)
}
@ -268,14 +269,13 @@ func TestWorkspace_createWithState(t *testing.T) {
}
b := backend.TestBackendConfig(t, inmem.New(), nil)
sMgr, err := b.State(workspace)
sMgr, err := b.StateMgr(workspace)
if err != nil {
t.Fatal(err)
}
newState := sMgr.State()
originalState.Version = newState.Version // the round-trip through the state manager implicitly populates version
if !originalState.Equal(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
}
states, err := b.States()
states, err := b.Workspaces()
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -94,7 +94,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
}
// we need the actual state to see if it's empty
sMgr, err := b.State(delEnv)
sMgr, err := b.StateMgr(delEnv)
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -134,7 +134,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
// be delegated from the Backend to the State itself.
stateLocker.Unlock(nil)
err = b.DeleteState(delEnv)
err = b.DeleteWorkspace(delEnv)
if err != nil {
c.Ui.Error(err.Error())
return 1

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package configload
import (
"fmt"
"path/filepath"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/registry"
@ -91,3 +92,35 @@ func (l *Loader) Sources() map[string][]byte {
func (l *Loader) IsConfigDir(path string) bool {
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"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/logutils"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
)
@ -674,17 +674,24 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
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
// are no attributes. We only set what is needed to perform a refresh.
state := terraform.NewState()
state.RootModule().Resources[name] = &terraform.ResourceState{
Type: r.Type,
Primary: &terraform.InstanceState{
ID: r.Primary.ID,
state := states.NewState()
state.RootModule().SetResourceInstanceCurrent(
addr,
&states.ResourceInstanceObjectSrc{
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
// 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.
actualR := state.RootModule().Resources[name]
actualR := state.ResourceInstance(absAddr)
if actualR == nil {
return fmt.Errorf("Resource gone!")
}
if actualR.Primary == nil {
if actualR.Current == nil {
return fmt.Errorf("Resource has no primary instance")
}
actual := actualR.Primary.Attributes
actual := actualR.Current.AttrsFlat
expected := r.Primary.Attributes
// Remove fields we're ignoring
for _, v := range c.IDRefreshIgnore {

View File

@ -3,12 +3,7 @@ package resource
import (
"errors"
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/terraform"
)
@ -20,149 +15,149 @@ func testStepConfig(
return testStep(opts, state, step)
}
func testStep(
opts terraform.ContextOpts,
state *terraform.State,
step TestStep) (*terraform.State, error) {
// Pre-taint any resources that have been defined in Taint, as long as this
// is not a destroy step.
if !step.Destroy {
if err := testStepTaint(state, step); err != nil {
func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) {
return nil, fmt.Errorf("testStep not yet updated for new state type")
/*
// Pre-taint any resources that have been defined in Taint, as long as this
// is not a destroy step.
if !step.Destroy {
if err := testStepTaint(state, step); err != nil {
return state, err
}
}
cfg, err := testConfig(opts, step)
if err != nil {
return state, err
}
}
cfg, err := testConfig(opts, step)
if err != nil {
return state, err
}
var stepDiags tfdiags.Diagnostics
var stepDiags tfdiags.Diagnostics
// Build the context
opts.Config = cfg
opts.State = state
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 {
// Build the context
opts.Config = cfg
opts.State = state
opts.Destroy = step.Destroy
ctx, stepDiags := terraform.NewContext(&opts)
if stepDiags.HasErrors() {
return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
}
log.Printf("[WARN] Config warnings:\n%s", stepDiags)
}
// 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)
}
if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
if stepDiags.HasErrors() {
return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
}
}
}
// Now, verify that Plan is now empty and we don't have a perpetual diff issue
// 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)
log.Printf("[WARN] Config warnings:\n%s", stepDiags)
}
}
// And another after a Refresh.
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
// Refresh!
state, stepDiags = ctx.Refresh()
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
// 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
// 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)
}
for _, moduleDiff := range p.Diff.Modules {
for k, instanceDiff := range moduleDiff.Resources {
if !strings.HasPrefix(k, "data.") {
empty = false
break
}
// 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()
if !instanceDiff.Destroy {
empty = false
// 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)
}
}
}
}
}
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)
// Now, verify that Plan is now empty and we don't have a perpetual diff issue
// 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)
}
}
}
// 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!")
}
// And another after a Refresh.
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
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!
return state, nil
// Data resources are tricky because they legitimately get instantiated
// 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 {

View File

@ -3,15 +3,12 @@ package resource
import (
"fmt"
"log"
"reflect"
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/hcl2/hcl"
"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"
)
@ -52,7 +49,7 @@ func testStepImportState(
}
opts.Config = cfg
opts.State = terraform.NewState()
opts.State = states.NewState()
ctx, stepDiags := terraform.NewContext(&opts)
if stepDiags.HasErrors() {
return state, stepDiags.Err()
@ -89,77 +86,84 @@ func testStepImportState(
// Go through the new state and verify
if step.ImportStateCheck != nil {
var states []*terraform.InstanceState
var states []*states.ResourceInstanceObjectSrc
for _, r := range newState.RootModule().Resources {
if r.Primary != nil {
states = append(states, r.Primary)
for _, i := range r.Instances {
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
}
}*/
}
// Verify that all the states match
if step.ImportStateVerify {
new := newState.RootModule().Resources
old := state.RootModule().Resources
for _, r := range new {
// Find the existing resource
var oldR *terraform.ResourceState
for _, r2 := range old {
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)
return nil, fmt.Errorf("testStepImportStep ImportStateVerify not yet updated for new state types")
/*
new := newState.RootModule().Resources
old := state.RootModule().Resources
for _, r := range new {
// Find the existing resource
var oldR *terraform.ResourceState
for _, r2 := range old {
if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type {
oldR = r2
break
}
}
for k, _ := range expected {
if strings.HasPrefix(k, v) {
delete(expected, k)
}
if oldR == nil {
return state, fmt.Errorf(
"Failed state verification, resource with ID %s not found",
r.Primary.ID)
}
}
if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
delete(actual, k)
// 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 strings.HasPrefix(k, v) {
delete(expected, 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))
if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
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.

View File

@ -16,7 +16,6 @@ import (
"github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/svchost/disco"
"github.com/hashicorp/terraform/terraform"
"github.com/mattn/go-colorable"
"github.com/mattn/go-shellwords"
"github.com/mitchellh/cli"
@ -113,9 +112,6 @@ func init() {
func wrappedMain() int {
var err error
// We always need to close the DebugInfo before we exit.
defer terraform.CloseDebugInfo()
log.SetOutput(os.Stderr)
log.Printf(
"[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
// object.
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)
}
// 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"
"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.
@ -44,6 +46,19 @@ type Backend struct {
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
// referenced throughout the receiving plan.
//

View File

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

View File

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

View File

@ -21,16 +21,16 @@ func TestTFPlanRoundTrip(t *testing.T) {
"foo": mustNewDynamicValueStr("foo value"),
},
Changes: &plans.Changes{
RootOutputs: map[string]*plans.OutputChange{
RootOutputs: map[string]*plans.OutputChangeSrc{
"bar": {
Change: plans.Change{
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
After: mustNewDynamicValueStr("bar value"),
},
Sensitive: false,
},
"baz": {
Change: plans.Change{
ChangeSrc: plans.ChangeSrc{
Action: plans.NoOp,
Before: mustNewDynamicValueStr("baz value"),
After: mustNewDynamicValueStr("baz value"),
@ -38,7 +38,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
Sensitive: false,
},
"secret": {
Change: plans.Change{
ChangeSrc: plans.ChangeSrc{
Action: plans.Update,
Before: mustNewDynamicValueStr("old secret value"),
After: mustNewDynamicValueStr("new secret value"),
@ -46,7 +46,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
Sensitive: true,
},
},
Resources: []*plans.ResourceInstanceChange{
Resources: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
@ -56,7 +56,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
ProviderAddr: addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
Change: plans.Change{
ChangeSrc: plans.ChangeSrc{
Action: plans.Replace,
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo-bar-baz"),
@ -76,7 +76,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
ProviderAddr: addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
Change: plans.Change{
ChangeSrc: plans.ChangeSrc{
Action: plans.Delete,
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
"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.
// This is used to be able to upgrade the schema in UpgradeResourceState.
type Schema struct {
Version int
Version uint64
Block *configschema.Block
}

View File

@ -3,7 +3,8 @@ package state
import (
"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
@ -18,7 +19,7 @@ type BackupState struct {
done bool
}
func (s *BackupState) State() *terraform.State {
func (s *BackupState) State() *states.State {
return s.Real.State()
}
@ -26,7 +27,7 @@ func (s *BackupState) RefreshState() error {
return s.Real.RefreshState()
}
func (s *BackupState) WriteState(state *terraform.State) error {
func (s *BackupState) WriteState(state *states.State) error {
s.mu.Lock()
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
// skip this with a nil state.
if state != nil {
ls := &LocalState{Path: s.Path}
ls := statemgr.NewFilesystem(s.Path)
if err := ls.WriteState(state); err != nil {
return err
}

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