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:
parent
cf6892275a
commit
a3403f2766
|
@ -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{},
|
||||
},
|
||||
``,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = `
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = `
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor {
|
|||
return nil
|
||||
}
|
||||
|
||||
names, _ := b.States()
|
||||
names, _ := b.Workspaces()
|
||||
return names
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
147
command/taint.go
147
command/taint.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
4
main.go
4
main.go
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue