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},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -80,7 +79,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -152,7 +150,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -178,7 +175,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
|
End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -250,7 +246,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
|
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -269,7 +264,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
|
End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -313,7 +307,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -333,7 +326,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
|
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -387,7 +379,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -433,7 +424,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
|
End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -469,7 +459,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
|
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -517,7 +506,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
|
End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -569,7 +557,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
@ -641,7 +628,6 @@ func TestParseRef(t *testing.T) {
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
||||||
},
|
},
|
||||||
Remaining: hcl.Traversal{},
|
|
||||||
},
|
},
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,6 +50,8 @@ type Backend struct {
|
||||||
opLock sync.Mutex
|
opLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ backend.Backend = (*Backend)(nil)
|
||||||
|
|
||||||
func (b *Backend) ConfigSchema() *configschema.Block {
|
func (b *Backend) ConfigSchema() *configschema.Block {
|
||||||
return &configschema.Block{
|
return &configschema.Block{
|
||||||
Attributes: map[string]*configschema.Attribute{
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
@ -160,15 +162,15 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,13 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
@ -25,25 +28,18 @@ import (
|
||||||
// backend must have. This state cannot be deleted.
|
// backend must have. This state cannot be deleted.
|
||||||
const DefaultStateName = "default"
|
const DefaultStateName = "default"
|
||||||
|
|
||||||
// This must be returned rather than a custom error so that the Terraform
|
// ErrWorkspacesNotSupported is an error returned when a caller attempts
|
||||||
// CLI can detect it and handle it appropriately.
|
// to perform an operation on a workspace other than "default" for a
|
||||||
var (
|
// backend that doesn't support multiple workspaces.
|
||||||
// ErrDefaultStateNotSupported is returned when an operation does not support
|
//
|
||||||
// using the default state, but requires a named state to be selected.
|
// The caller can detect this to do special fallback behavior or produce
|
||||||
ErrDefaultStateNotSupported = errors.New("default state not supported\n" +
|
// a specific, helpful error message.
|
||||||
"You can create a new workspace with the \"workspace new\" command.")
|
var ErrWorkspacesNotSupported = errors.New("workspaces not supported")
|
||||||
|
|
||||||
// ErrNamedStatesNotSupported is returned when a named state operation
|
// ErrNamedStatesNotSupported is an older name for ErrWorkspacesNotSupported.
|
||||||
// isn't supported.
|
//
|
||||||
ErrNamedStatesNotSupported = errors.New("named states not supported")
|
// Deprecated: Use ErrWorkspacesNotSupported instead.
|
||||||
|
var ErrNamedStatesNotSupported = ErrWorkspacesNotSupported
|
||||||
// ErrOperationNotSupported is returned when an unsupported operation
|
|
||||||
// is detected by the configured backend.
|
|
||||||
ErrOperationNotSupported = errors.New("operation not supported")
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitFn is used to initialize a new backend.
|
|
||||||
type InitFn func() Backend
|
|
||||||
|
|
||||||
// Backend is the minimal interface that must be implemented to enable Terraform.
|
// Backend is the minimal interface that must be implemented to enable Terraform.
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
|
@ -87,25 +83,26 @@ type Backend interface {
|
||||||
// is undefined and no other methods may be called.
|
// is undefined and no other methods may be called.
|
||||||
Configure(cty.Value) tfdiags.Diagnostics
|
Configure(cty.Value) tfdiags.Diagnostics
|
||||||
|
|
||||||
// State returns the current state for this environment. This state may
|
// StateMgr returns the state manager for the given workspace name.
|
||||||
// not be loaded locally: the proper APIs should be called on state.State
|
|
||||||
// to load the state. If the state.State is a state.Locker, it's up to the
|
|
||||||
// caller to call Lock and Unlock as needed.
|
|
||||||
//
|
//
|
||||||
// If the named state doesn't exist it will be created. The "default" state
|
// If the returned state manager also implements statemgr.Locker then
|
||||||
// is always assumed to exist.
|
// it's the caller's responsibility to call Lock and Unlock as appropriate.
|
||||||
State(name string) (state.State, error)
|
|
||||||
|
|
||||||
// DeleteState removes the named state if it exists. It is an error
|
|
||||||
// to delete the default state.
|
|
||||||
//
|
//
|
||||||
// DeleteState does not prevent deleting a state that is in use. It is the
|
// If the named workspace doesn't exist, or if it has no state, it will
|
||||||
// responsibility of the caller to hold a Lock on the state when calling
|
// be created either immediately on this call or the first time
|
||||||
// this method.
|
// PersistState is called, depending on the state manager implementation.
|
||||||
DeleteState(name string) error
|
StateMgr(workspace string) (statemgr.Full, error)
|
||||||
|
|
||||||
// States returns a list of configured named states.
|
// DeleteWorkspace removes the workspace with the given name if it exists.
|
||||||
States() ([]string, error)
|
//
|
||||||
|
// DeleteWorkspace cannot prevent deleting a state that is in use. It is
|
||||||
|
// the responsibility of the caller to hold a Lock for the state manager
|
||||||
|
// belonging to this workspace before calling this method.
|
||||||
|
DeleteWorkspace(name string) error
|
||||||
|
|
||||||
|
// States returns a list of the names of all of the workspaces that exist
|
||||||
|
// in this backend.
|
||||||
|
Workspaces() ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced implements additional behavior on top of a normal backend.
|
// Enhanced implements additional behavior on top of a normal backend.
|
||||||
|
@ -136,7 +133,7 @@ type Enhanced interface {
|
||||||
type Local interface {
|
type Local interface {
|
||||||
// Context returns a runnable terraform Context. The operation parameter
|
// Context returns a runnable terraform Context. The operation parameter
|
||||||
// doesn't need a Type set but it needs other options set such as Module.
|
// doesn't need a Type set but it needs other options set such as Module.
|
||||||
Context(*Operation) (*terraform.Context, state.State, tfdiags.Diagnostics)
|
Context(*Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An operation represents an operation for Terraform to execute.
|
// An operation represents an operation for Terraform to execute.
|
||||||
|
@ -166,7 +163,7 @@ type Operation struct {
|
||||||
PlanId string
|
PlanId string
|
||||||
PlanRefresh bool // PlanRefresh will do a refresh before a plan
|
PlanRefresh bool // PlanRefresh will do a refresh before a plan
|
||||||
PlanOutPath string // PlanOutPath is the path to save the plan
|
PlanOutPath string // PlanOutPath is the path to save the plan
|
||||||
PlanOutBackend *terraform.BackendState
|
PlanOutBackend *plans.Backend
|
||||||
|
|
||||||
// ConfigDir is the path to the directory containing the configuration's
|
// ConfigDir is the path to the directory containing the configuration's
|
||||||
// root module.
|
// root module.
|
||||||
|
@ -178,11 +175,10 @@ type Operation struct {
|
||||||
|
|
||||||
// Plan is a plan that was passed as an argument. This is valid for
|
// Plan is a plan that was passed as an argument. This is valid for
|
||||||
// plan and apply arguments but may not work for all backends.
|
// plan and apply arguments but may not work for all backends.
|
||||||
Plan *terraform.Plan
|
PlanFile *planfile.Reader
|
||||||
|
|
||||||
// The options below are more self-explanatory and affect the runtime
|
// The options below are more self-explanatory and affect the runtime
|
||||||
// behavior of the operation.
|
// behavior of the operation.
|
||||||
AutoApprove bool
|
|
||||||
Destroy bool
|
Destroy bool
|
||||||
Targets []addrs.Targetable
|
Targets []addrs.Targetable
|
||||||
Variables map[string]UnparsedVariableValue
|
Variables map[string]UnparsedVariableValue
|
||||||
|
@ -259,7 +255,7 @@ type RunningOperation struct {
|
||||||
// State is the final state after the operation completed. Persisting
|
// State is the final state after the operation completed. Persisting
|
||||||
// this state is managed by the backend. This should only be read
|
// this state is managed by the backend. This should only be read
|
||||||
// after the operation completes to avoid read/write races.
|
// after the operation completes to avoid read/write races.
|
||||||
State *terraform.State
|
State *states.State
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperationResult describes the result status of an operation.
|
// OperationResult describes the result status of an operation.
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
@ -65,7 +65,7 @@ type Local struct {
|
||||||
|
|
||||||
// We only want to create a single instance of a local state, so store them
|
// We only want to create a single instance of a local state, so store them
|
||||||
// here as they're loaded.
|
// here as they're loaded.
|
||||||
states map[string]state.State
|
states map[string]statemgr.Full
|
||||||
|
|
||||||
// Terraform context. Many of these will be overridden or merged by
|
// Terraform context. Many of these will be overridden or merged by
|
||||||
// Operation. See Operation for more details.
|
// Operation. See Operation for more details.
|
||||||
|
@ -97,8 +97,11 @@ type Local struct {
|
||||||
RunningInAutomation bool
|
RunningInAutomation bool
|
||||||
|
|
||||||
opLock sync.Mutex
|
opLock sync.Mutex
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ backend.Backend = (*Local)(nil)
|
||||||
|
|
||||||
func (b *Local) ConfigSchema() *configschema.Block {
|
func (b *Local) ConfigSchema() *configschema.Block {
|
||||||
if b.Backend != nil {
|
if b.Backend != nil {
|
||||||
return b.Backend.ConfigSchema()
|
return b.Backend.ConfigSchema()
|
||||||
|
@ -184,67 +187,10 @@ func (b *Local) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Local) State(name string) (state.State, error) {
|
func (b *Local) Workspaces() ([]string, error) {
|
||||||
statePath, stateOutPath, backupPath := b.StatePaths(name)
|
|
||||||
|
|
||||||
// If we have a backend handling state, delegate to that.
|
|
||||||
if b.Backend != nil {
|
|
||||||
return b.Backend.State(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := b.states[name]; ok {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.createState(name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we need to load the state.
|
|
||||||
var s state.State = &state.LocalState{
|
|
||||||
Path: statePath,
|
|
||||||
PathOut: stateOutPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are backing up the state, wrap it
|
|
||||||
if backupPath != "" {
|
|
||||||
s = &state.BackupState{
|
|
||||||
Real: s,
|
|
||||||
Path: backupPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.states == nil {
|
|
||||||
b.states = map[string]state.State{}
|
|
||||||
}
|
|
||||||
b.states[name] = s
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteState removes a named state.
|
|
||||||
// The "default" state cannot be removed.
|
|
||||||
func (b *Local) DeleteState(name string) error {
|
|
||||||
// If we have a backend handling state, defer to that.
|
// If we have a backend handling state, defer to that.
|
||||||
if b.Backend != nil {
|
if b.Backend != nil {
|
||||||
return b.Backend.DeleteState(name)
|
return b.Backend.Workspaces()
|
||||||
}
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
return errors.New("empty state name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == backend.DefaultStateName {
|
|
||||||
return errors.New("cannot delete default state")
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(b.states, name)
|
|
||||||
return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Local) States() ([]string, error) {
|
|
||||||
// If we have a backend handling state, defer to that.
|
|
||||||
if b.Backend != nil {
|
|
||||||
return b.Backend.States()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the listing always start with "default"
|
// the listing always start with "default"
|
||||||
|
@ -272,6 +218,55 @@ func (b *Local) States() ([]string, error) {
|
||||||
return envs, nil
|
return envs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteWorkspace removes a workspace.
|
||||||
|
//
|
||||||
|
// The "default" workspace cannot be removed.
|
||||||
|
func (b *Local) DeleteWorkspace(name string) error {
|
||||||
|
// If we have a backend handling state, defer to that.
|
||||||
|
if b.Backend != nil {
|
||||||
|
return b.Backend.DeleteWorkspace(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("empty state name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == backend.DefaultStateName {
|
||||||
|
return errors.New("cannot delete default state")
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(b.states, name)
|
||||||
|
return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Local) StateMgr(name string) (statemgr.Full, error) {
|
||||||
|
statePath, stateOutPath, backupPath := b.StatePaths(name)
|
||||||
|
|
||||||
|
// If we have a backend handling state, delegate to that.
|
||||||
|
if b.Backend != nil {
|
||||||
|
return b.Backend.StateMgr(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := b.states[name]; ok {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.createState(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := statemgr.NewFilesystemBetweenPaths(statePath, stateOutPath)
|
||||||
|
if backupPath != "" {
|
||||||
|
s.SetBackupPath(backupPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.states == nil {
|
||||||
|
b.states = map[string]statemgr.Full{}
|
||||||
|
}
|
||||||
|
b.states[name] = s
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Operation implements backend.Enhanced
|
// Operation implements backend.Enhanced
|
||||||
//
|
//
|
||||||
// This will initialize an in-memory terraform.Context to perform the
|
// This will initialize an in-memory terraform.Context to perform the
|
||||||
|
@ -347,14 +342,14 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
|
||||||
return runningOp, nil
|
return runningOp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opWait waits for the operation to complete, and a stop signal or a
|
// opWait wats for the operation to complete, and a stop signal or a
|
||||||
// cancelation signal.
|
// cancelation signal.
|
||||||
func (b *Local) opWait(
|
func (b *Local) opWait(
|
||||||
doneCh <-chan struct{},
|
doneCh <-chan struct{},
|
||||||
stopCtx context.Context,
|
stopCtx context.Context,
|
||||||
cancelCtx context.Context,
|
cancelCtx context.Context,
|
||||||
tfCtx *terraform.Context,
|
tfCtx *terraform.Context,
|
||||||
opState state.State) (canceled bool) {
|
opStateMgr statemgr.Persister) (canceled bool) {
|
||||||
// Wait for the operation to finish or for us to be interrupted so
|
// Wait for the operation to finish or for us to be interrupted so
|
||||||
// we can handle it properly.
|
// we can handle it properly.
|
||||||
select {
|
select {
|
||||||
|
@ -365,7 +360,7 @@ func (b *Local) opWait(
|
||||||
|
|
||||||
// try to force a PersistState just in case the process is terminated
|
// try to force a PersistState just in case the process is terminated
|
||||||
// before we can complete.
|
// before we can complete.
|
||||||
if err := opState.PersistState(); err != nil {
|
if err := opStateMgr.PersistState(); err != nil {
|
||||||
// We can't error out from here, but warn the user if there was an error.
|
// We can't error out from here, but warn the user if there was an error.
|
||||||
// If this isn't transient, we will catch it again below, and
|
// If this isn't transient, we will catch it again below, and
|
||||||
// attempt to save the state another way.
|
// attempt to save the state another way.
|
||||||
|
@ -465,7 +460,7 @@ func (b *Local) schemaConfigure(ctx context.Context) error {
|
||||||
|
|
||||||
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
|
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
|
||||||
// configured from the CLI.
|
// configured from the CLI.
|
||||||
func (b *Local) StatePaths(name string) (string, string, string) {
|
func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) {
|
||||||
statePath := b.StatePath
|
statePath := b.StatePath
|
||||||
stateOutPath := b.StateOutPath
|
stateOutPath := b.StateOutPath
|
||||||
backupPath := b.StateBackupPath
|
backupPath := b.StateBackupPath
|
||||||
|
|
|
@ -8,9 +8,12 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
@ -23,10 +26,11 @@ func (b *Local) opApply(
|
||||||
log.Printf("[INFO] backend/local: starting Apply operation")
|
log.Printf("[INFO] backend/local: starting Apply operation")
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
var err error
|
||||||
|
|
||||||
// If we have a nil module at this point, then set it to an empty tree
|
// If we have a nil module at this point, then set it to an empty tree
|
||||||
// to avoid any potential crashes.
|
// to avoid any potential crashes.
|
||||||
if op.Plan == nil && !op.Destroy && !op.HasConfig() {
|
if op.PlanFile == nil && !op.Destroy && !op.HasConfig() {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"No configuration files",
|
"No configuration files",
|
||||||
|
@ -47,9 +51,9 @@ func (b *Local) opApply(
|
||||||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
|
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
|
||||||
|
|
||||||
// Get our context
|
// Get our context
|
||||||
tfCtx, opState, err := b.context(op)
|
tfCtx, _, opState, contextDiags := b.context(op)
|
||||||
if err != nil {
|
diags = diags.Append(contextDiags)
|
||||||
diags = diags.Append(err)
|
if contextDiags.HasErrors() {
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -58,7 +62,7 @@ func (b *Local) opApply(
|
||||||
runningOp.State = tfCtx.State()
|
runningOp.State = tfCtx.State()
|
||||||
|
|
||||||
// If we weren't given a plan, then we refresh/plan
|
// If we weren't given a plan, then we refresh/plan
|
||||||
if op.Plan == nil {
|
if op.PlanFile == nil {
|
||||||
// If we're refreshing before apply, perform that
|
// If we're refreshing before apply, perform that
|
||||||
if op.PlanRefresh {
|
if op.PlanRefresh {
|
||||||
log.Printf("[INFO] backend/local: apply calling Refresh")
|
log.Printf("[INFO] backend/local: apply calling Refresh")
|
||||||
|
@ -80,7 +84,7 @@ func (b *Local) opApply(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispPlan := format.NewPlan(plan)
|
dispPlan := format.NewPlan(plan.Changes)
|
||||||
trivialPlan := dispPlan.Empty()
|
trivialPlan := dispPlan.Empty()
|
||||||
hasUI := op.UIOut != nil && op.UIIn != nil
|
hasUI := op.UIOut != nil && op.UIIn != nil
|
||||||
mustConfirm := hasUI && ((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove && !trivialPlan))
|
mustConfirm := hasUI && ((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove && !trivialPlan))
|
||||||
|
@ -133,10 +137,10 @@ func (b *Local) opApply(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our hook for continuous state updates
|
// Setup our hook for continuous state updates
|
||||||
stateHook.State = opState
|
stateHook.StateMgr = opState
|
||||||
|
|
||||||
// Start the apply in a goroutine so that we can be interrupted.
|
// Start the apply in a goroutine so that we can be interrupted.
|
||||||
var applyState *terraform.State
|
var applyState *states.State
|
||||||
var applyDiags tfdiags.Diagnostics
|
var applyDiags tfdiags.Diagnostics
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -152,14 +156,8 @@ func (b *Local) opApply(
|
||||||
|
|
||||||
// Store the final state
|
// Store the final state
|
||||||
runningOp.State = applyState
|
runningOp.State = applyState
|
||||||
|
err = statemgr.WriteAndPersist(opState, applyState)
|
||||||
// Persist the state
|
if err != nil {
|
||||||
if err := opState.WriteState(applyState); err != nil {
|
|
||||||
diags = diags.Append(b.backupStateForError(applyState, err))
|
|
||||||
b.ReportResult(runningOp, diags)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := opState.PersistState(); err != nil {
|
|
||||||
diags = diags.Append(b.backupStateForError(applyState, err))
|
diags = diags.Append(b.backupStateForError(applyState, err))
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
|
@ -211,10 +209,10 @@ func (b *Local) opApply(
|
||||||
// to local disk to help the user recover. This is a "last ditch effort" sort
|
// to local disk to help the user recover. This is a "last ditch effort" sort
|
||||||
// of thing, so we really don't want to end up in this codepath; we should do
|
// of thing, so we really don't want to end up in this codepath; we should do
|
||||||
// everything we possibly can to get the state saved _somewhere_.
|
// everything we possibly can to get the state saved _somewhere_.
|
||||||
func (b *Local) backupStateForError(applyState *terraform.State, err error) error {
|
func (b *Local) backupStateForError(applyState *states.State, err error) error {
|
||||||
b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err))
|
b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err))
|
||||||
|
|
||||||
local := &state.LocalState{Path: "errored.tfstate"}
|
local := statemgr.NewFilesystem("errored.tfstate")
|
||||||
writeErr := local.WriteState(applyState)
|
writeErr := local.WriteState(applyState)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
b.CLI.Error(fmt.Sprintf(
|
b.CLI.Error(fmt.Sprintf(
|
||||||
|
@ -226,7 +224,10 @@ func (b *Local) backupStateForError(applyState *terraform.State, err error) erro
|
||||||
// but at least the user has _some_ path to recover if we end up
|
// but at least the user has _some_ path to recover if we end up
|
||||||
// here for some reason.
|
// here for some reason.
|
||||||
stateBuf := new(bytes.Buffer)
|
stateBuf := new(bytes.Buffer)
|
||||||
jsonErr := terraform.WriteState(applyState, stateBuf)
|
stateFile := &statefile.File{
|
||||||
|
State: applyState,
|
||||||
|
}
|
||||||
|
jsonErr := statefile.Write(stateFile, stateBuf)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
b.CLI.Error(fmt.Sprintf(
|
b.CLI.Error(fmt.Sprintf(
|
||||||
"Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr,
|
"Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr,
|
||||||
|
|
|
@ -10,13 +10,15 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
|
||||||
"github.com/hashicorp/terraform/state"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocal_applyBasic(t *testing.T) {
|
func TestLocal_applyBasic(t *testing.T) {
|
||||||
|
@ -226,19 +228,17 @@ type backendWithFailingState struct {
|
||||||
Local
|
Local
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backendWithFailingState) State(name string) (state.State, error) {
|
func (b *backendWithFailingState) StateMgr(name string) (statemgr.Full, error) {
|
||||||
return &failingState{
|
return &failingState{
|
||||||
&state.LocalState{
|
statemgr.NewFilesystem("failing-state.tfstate"),
|
||||||
Path: "failing-state.tfstate",
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type failingState struct {
|
type failingState struct {
|
||||||
*state.LocalState
|
*statemgr.Filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s failingState) WriteState(state *terraform.State) error {
|
func (s failingState) WriteState(state *states.State) error {
|
||||||
return errors.New("fake failure")
|
return errors.New("fake failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,21 @@ package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// backend.Local implementation.
|
// backend.Local implementation.
|
||||||
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) {
|
func (b *Local) Context(op *backend.Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics) {
|
||||||
// Make sure the type is invalid. We use this as a way to know not
|
// Make sure the type is invalid. We use this as a way to know not
|
||||||
// to ask for input/validate.
|
// to ask for input/validate.
|
||||||
op.Type = backend.OperationTypeInvalid
|
op.Type = backend.OperationTypeInvalid
|
||||||
|
@ -24,27 +27,26 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State,
|
||||||
op.StateLocker = clistate.NewNoopLocker()
|
op.StateLocker = clistate.NewNoopLocker()
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.context(op)
|
ctx, _, stateMgr, diags := b.context(op)
|
||||||
|
return ctx, stateMgr, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) {
|
func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.Snapshot, statemgr.Full, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Get the state.
|
// Get the latest state.
|
||||||
s, err := b.State(op.Workspace)
|
s, err := b.StateMgr(op.Workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||||
return nil, nil, diags
|
return nil, nil, nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
|
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
|
||||||
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
|
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
|
||||||
return nil, nil, diags
|
return nil, nil, nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.RefreshState(); err != nil {
|
if err := s.RefreshState(); err != nil {
|
||||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||||
return nil, nil, diags
|
return nil, nil, nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize our context options
|
// Initialize our context options
|
||||||
|
@ -58,8 +60,55 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
||||||
opts.Targets = op.Targets
|
opts.Targets = op.Targets
|
||||||
opts.UIInput = op.UIIn
|
opts.UIInput = op.UIIn
|
||||||
|
|
||||||
|
// Load the latest state. If we enter contextFromPlanFile below then the
|
||||||
|
// state snapshot in the plan file must match this, or else it'll return
|
||||||
|
// error diagnostics.
|
||||||
|
opts.State = s.State()
|
||||||
|
|
||||||
|
var tfCtx *terraform.Context
|
||||||
|
var ctxDiags tfdiags.Diagnostics
|
||||||
|
var configSnap *configload.Snapshot
|
||||||
|
if op.PlanFile != nil {
|
||||||
|
tfCtx, configSnap, ctxDiags = b.contextFromPlanFile(op.PlanFile, opts)
|
||||||
|
// Write sources into the cache of the main loader so that they are
|
||||||
|
// available if we need to generate diagnostic message snippets.
|
||||||
|
op.ConfigLoader.ImportSourcesFromSnapshot(configSnap)
|
||||||
|
} else {
|
||||||
|
tfCtx, configSnap, ctxDiags = b.contextDirect(op, opts)
|
||||||
|
}
|
||||||
|
diags = diags.Append(ctxDiags)
|
||||||
|
|
||||||
|
// If we have an operation, then we automatically do the input/validate
|
||||||
|
// here since every option requires this.
|
||||||
|
if op.Type != backend.OperationTypeInvalid {
|
||||||
|
// If input asking is enabled, then do that
|
||||||
|
if op.PlanFile == nil && b.OpInput {
|
||||||
|
mode := terraform.InputModeProvider
|
||||||
|
mode |= terraform.InputModeVar
|
||||||
|
mode |= terraform.InputModeVarUnset
|
||||||
|
|
||||||
|
inputDiags := tfCtx.Input(mode)
|
||||||
|
diags = diags.Append(inputDiags)
|
||||||
|
if inputDiags.HasErrors() {
|
||||||
|
return nil, nil, nil, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If validation is enabled, validate
|
||||||
|
if b.OpValidation {
|
||||||
|
validateDiags := tfCtx.Validate()
|
||||||
|
diags = diags.Append(validateDiags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tfCtx, configSnap, s, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Local) contextDirect(op *backend.Operation, opts terraform.ContextOpts) (*terraform.Context, *configload.Snapshot, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Load the configuration using the caller-provided configuration loader.
|
// Load the configuration using the caller-provided configuration loader.
|
||||||
config, configDiags := op.ConfigLoader.LoadConfig(op.ConfigDir)
|
config, configSnap, configDiags := op.ConfigLoader.LoadConfigWithSnapshot(op.ConfigDir)
|
||||||
diags = diags.Append(configDiags)
|
diags = diags.Append(configDiags)
|
||||||
if configDiags.HasErrors() {
|
if configDiags.HasErrors() {
|
||||||
return nil, nil, diags
|
return nil, nil, diags
|
||||||
|
@ -75,50 +124,80 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
||||||
opts.Variables = variables
|
opts.Variables = variables
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load our state
|
tfCtx, ctxDiags := terraform.NewContext(&opts)
|
||||||
// By the time we get here, the backend creation code in "command" took
|
|
||||||
// care of making s.State() return a state compatible with our plan,
|
|
||||||
// if any, so we can safely pass this value in both the plan context
|
|
||||||
// and new context cases below.
|
|
||||||
opts.State = s.State()
|
|
||||||
|
|
||||||
// Build the context
|
|
||||||
var tfCtx *terraform.Context
|
|
||||||
var ctxDiags tfdiags.Diagnostics
|
|
||||||
if op.Plan != nil {
|
|
||||||
tfCtx, ctxDiags = op.Plan.Context(&opts)
|
|
||||||
} else {
|
|
||||||
tfCtx, ctxDiags = terraform.NewContext(&opts)
|
|
||||||
}
|
|
||||||
diags = diags.Append(ctxDiags)
|
diags = diags.Append(ctxDiags)
|
||||||
if ctxDiags.HasErrors() {
|
return tfCtx, configSnap, diags
|
||||||
return nil, nil, diags
|
}
|
||||||
|
|
||||||
|
func (b *Local) contextFromPlanFile(pf *planfile.Reader, opts terraform.ContextOpts) (*terraform.Context, *configload.Snapshot, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
const errSummary = "Invalid plan file"
|
||||||
|
|
||||||
|
// A plan file has a snapshot of configuration embedded inside it, which
|
||||||
|
// is used instead of whatever configuration might be already present
|
||||||
|
// in the filesystem.
|
||||||
|
snap, err := pf.ReadConfigSnapshot()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
errSummary,
|
||||||
|
fmt.Sprintf("Failed to read configuration snapshot from plan file: %s.", err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
loader := configload.NewLoaderFromSnapshot(snap)
|
||||||
|
config, configDiags := loader.LoadConfig(snap.Modules[""].Dir)
|
||||||
|
diags = diags.Append(configDiags)
|
||||||
|
if configDiags.HasErrors() {
|
||||||
|
return nil, snap, diags
|
||||||
|
}
|
||||||
|
opts.Config = config
|
||||||
|
|
||||||
|
plan, err := pf.ReadPlan()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
errSummary,
|
||||||
|
fmt.Sprintf("Failed to read plan from plan file: %s.", err),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have an operation, then we automatically do the input/validate
|
variables := terraform.InputValues{}
|
||||||
// here since every option requires this.
|
for name, dyVal := range plan.VariableValues {
|
||||||
if op.Type != backend.OperationTypeInvalid {
|
ty, err := dyVal.ImpliedType()
|
||||||
// If input asking is enabled, then do that
|
if err != nil {
|
||||||
if op.Plan == nil && b.OpInput {
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
mode := terraform.InputModeProvider
|
tfdiags.Error,
|
||||||
mode |= terraform.InputModeVar
|
errSummary,
|
||||||
mode |= terraform.InputModeVarUnset
|
fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err),
|
||||||
|
))
|
||||||
inputDiags := tfCtx.Input(mode)
|
continue
|
||||||
diags = diags.Append(inputDiags)
|
}
|
||||||
if inputDiags.HasErrors() {
|
val, err := dyVal.Decode(ty)
|
||||||
return nil, nil, diags
|
if err != nil {
|
||||||
}
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
errSummary,
|
||||||
|
fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err),
|
||||||
|
))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If validation is enabled, validate
|
variables[name] = &terraform.InputValue{
|
||||||
if b.OpValidation {
|
Value: val,
|
||||||
validateDiags := tfCtx.Validate()
|
SourceType: terraform.ValueFromPlan,
|
||||||
diags = diags.Append(validateDiags)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
opts.Variables = variables
|
||||||
|
|
||||||
return tfCtx, s, diags
|
// TODO: populate the changes (formerly diff)
|
||||||
|
// TODO: targets
|
||||||
|
// TODO: check that the states match
|
||||||
|
// TODO: impose provider SHA256 constraints
|
||||||
|
|
||||||
|
tfCtx, ctxDiags := terraform.NewContext(&opts)
|
||||||
|
diags = diags.Append(ctxDiags)
|
||||||
|
return tfCtx, snap, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateWarnHeader = `
|
const validateWarnHeader = `
|
||||||
|
|
|
@ -5,14 +5,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Local) opPlan(
|
func (b *Local) opPlan(
|
||||||
|
@ -24,8 +25,9 @@ func (b *Local) opPlan(
|
||||||
log.Printf("[INFO] backend/local: starting Plan operation")
|
log.Printf("[INFO] backend/local: starting Plan operation")
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
var err error
|
||||||
|
|
||||||
if b.CLI != nil && op.Plan != nil {
|
if b.CLI != nil && op.PlanFile != nil {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"Can't re-plan a saved plan",
|
"Can't re-plan a saved plan",
|
||||||
|
@ -56,9 +58,9 @@ func (b *Local) opPlan(
|
||||||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
|
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
|
||||||
|
|
||||||
// Get our context
|
// Get our context
|
||||||
tfCtx, opState, err := b.context(op)
|
tfCtx, configSnap, opState, ctxDiags := b.context(op)
|
||||||
if err != nil {
|
diags = diags.Append(ctxDiags)
|
||||||
diags = diags.Append(err)
|
if ctxDiags.HasErrors() {
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -67,6 +69,7 @@ func (b *Local) opPlan(
|
||||||
runningOp.State = tfCtx.State()
|
runningOp.State = tfCtx.State()
|
||||||
|
|
||||||
// If we're refreshing before plan, perform that
|
// If we're refreshing before plan, perform that
|
||||||
|
baseState := runningOp.State
|
||||||
if op.PlanRefresh {
|
if op.PlanRefresh {
|
||||||
log.Printf("[INFO] backend/local: plan calling Refresh")
|
log.Printf("[INFO] backend/local: plan calling Refresh")
|
||||||
|
|
||||||
|
@ -74,19 +77,20 @@ func (b *Local) opPlan(
|
||||||
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
|
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := tfCtx.Refresh()
|
refreshedState, err := tfCtx.Refresh()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
baseState = refreshedState // plan will be relative to our refreshed state
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
b.CLI.Output("\n------------------------------------------------------------------------")
|
b.CLI.Output("\n------------------------------------------------------------------------")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the plan in a goroutine so we can be interrupted
|
// Perform the plan in a goroutine so we can be interrupted
|
||||||
var plan *terraform.Plan
|
var plan *plans.Plan
|
||||||
var planDiags tfdiags.Diagnostics
|
var planDiags tfdiags.Diagnostics
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -105,25 +109,19 @@ func (b *Local) opPlan(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Record state
|
// Record state
|
||||||
runningOp.PlanEmpty = plan.Diff.Empty()
|
runningOp.PlanEmpty = plan.Changes.Empty()
|
||||||
|
|
||||||
// Save the plan to disk
|
// Save the plan to disk
|
||||||
if path := op.PlanOutPath; path != "" {
|
if path := op.PlanOutPath; path != "" {
|
||||||
// Write the backend if we have one
|
plan.Backend = *op.PlanOutBackend
|
||||||
plan.Backend = op.PlanOutBackend
|
|
||||||
|
|
||||||
// This works around a bug (#12871) which is no longer possible to
|
// We may have updated the state in the refresh step above, but we
|
||||||
// trigger but will exist for already corrupted upgrades.
|
// will freeze that updated state in the plan file for now and
|
||||||
if plan.Backend != nil && plan.State != nil {
|
// only write it if this plan is subsequently applied.
|
||||||
plan.State.Remote = nil
|
plannedStateFile := statemgr.PlannedStateUpdate(opState, baseState)
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[INFO] backend/local: writing plan output to: %s", path)
|
log.Printf("[INFO] backend/local: writing plan output to: %s", path)
|
||||||
f, err := os.Create(path)
|
err = planfile.Create(path, configSnap, plannedStateFile, plan)
|
||||||
if err == nil {
|
|
||||||
err = terraform.WritePlan(plan, f)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
@ -137,7 +135,7 @@ func (b *Local) opPlan(
|
||||||
|
|
||||||
// Perform some output tasks if we have a CLI to output to.
|
// Perform some output tasks if we have a CLI to output to.
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
dispPlan := format.NewPlan(plan)
|
dispPlan := format.NewPlan(plan.Changes)
|
||||||
if dispPlan.Empty() {
|
if dispPlan.Empty() {
|
||||||
b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
|
b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
|
||||||
return
|
return
|
||||||
|
|
|
@ -9,7 +9,8 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ func (b *Local) opRefresh(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get our context
|
// Get our context
|
||||||
tfCtx, opState, contextDiags := b.context(op)
|
tfCtx, _, opState, contextDiags := b.context(op)
|
||||||
diags = diags.Append(contextDiags)
|
diags = diags.Append(contextDiags)
|
||||||
if contextDiags.HasErrors() {
|
if contextDiags.HasErrors() {
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
|
@ -51,15 +52,19 @@ func (b *Local) opRefresh(
|
||||||
|
|
||||||
// Set our state
|
// Set our state
|
||||||
runningOp.State = opState.State()
|
runningOp.State = opState.State()
|
||||||
if runningOp.State.Empty() || !runningOp.State.HasResources() {
|
if !runningOp.State.HasResources() {
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
b.CLI.Output(b.Colorize().Color(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
strings.TrimSpace(refreshNoState) + "\n"))
|
tfdiags.Warning,
|
||||||
|
"Empty or non-existent state",
|
||||||
|
"There are currently no resources tracked in the state, so there is nothing to refresh.",
|
||||||
|
))
|
||||||
|
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(refreshNoState) + "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the refresh in a goroutine so we can be interrupted
|
// Perform the refresh in a goroutine so we can be interrupted
|
||||||
var newState *terraform.State
|
var newState *states.State
|
||||||
var refreshDiags tfdiags.Diagnostics
|
var refreshDiags tfdiags.Diagnostics
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -80,17 +85,12 @@ func (b *Local) opRefresh(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write and persist the state
|
err := statemgr.WriteAndPersist(opState, newState)
|
||||||
if err := opState.WriteState(newState); err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
|
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := opState.PersistState(); err != nil {
|
|
||||||
diags = diags.Append(errwrap.Wrapf("Failed to save state: {{err}}", err))
|
|
||||||
b.ReportResult(runningOp, diags)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshNoState = `
|
const refreshNoState = `
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
dflt := backend.DefaultStateName
|
dflt := backend.DefaultStateName
|
||||||
expectedStates := []string{dflt}
|
expectedStates := []string{dflt}
|
||||||
|
|
||||||
b := New()
|
b := &Local{}
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -105,11 +105,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedA := "test_A"
|
expectedA := "test_A"
|
||||||
if _, err := b.State(expectedA); err != nil {
|
if _, err := b.StateMgr(expectedA); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err = b.States()
|
states, err = b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedB := "test_B"
|
expectedB := "test_B"
|
||||||
if _, err := b.State(expectedB); err != nil {
|
if _, err := b.StateMgr(expectedB); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err = b.States()
|
states, err = b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -134,11 +134,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
t.Fatalf("expected %q, got %q", expectedStates, states)
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.DeleteState(expectedA); err != nil {
|
if err := b.DeleteWorkspace(expectedA); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err = b.States()
|
states, err = b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -148,11 +148,11 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
t.Fatalf("expected %q, got %q", expectedStates, states)
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.DeleteState(expectedB); err != nil {
|
if err := b.DeleteWorkspace(expectedB); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err = b.States()
|
states, err = b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
|
||||||
t.Fatalf("expected %q, got %q", expectedStates, states)
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.DeleteState(dflt); err == nil {
|
if err := b.DeleteWorkspace(dflt); err == nil {
|
||||||
t.Fatal("expected error deleting default state")
|
t.Fatal("expected error deleting default state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,14 +182,11 @@ var errTestDelegateState = errors.New("State called")
|
||||||
var errTestDelegateStates = errors.New("States called")
|
var errTestDelegateStates = errors.New("States called")
|
||||||
var errTestDelegateDeleteState = errors.New("Delete called")
|
var errTestDelegateDeleteState = errors.New("Delete called")
|
||||||
|
|
||||||
func (b *testDelegateBackend) State(name string) (state.State, error) {
|
func (b *testDelegateBackend) State(name string) (statemgr.Full, error) {
|
||||||
if b.stateErr {
|
if b.stateErr {
|
||||||
return nil, errTestDelegateState
|
return nil, errTestDelegateState
|
||||||
}
|
}
|
||||||
s := &state.LocalState{
|
s := statemgr.NewFilesystem("terraform.tfstate")
|
||||||
Path: "terraform.tfstate",
|
|
||||||
PathOut: "terraform.tfstate",
|
|
||||||
}
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,15 +213,15 @@ func TestLocal_multiStateBackend(t *testing.T) {
|
||||||
deleteErr: true,
|
deleteErr: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, err := b.State("test"); err != errTestDelegateState {
|
if _, err := b.StateMgr("test"); err != errTestDelegateState {
|
||||||
t.Fatal("expected errTestDelegateState, got:", err)
|
t.Fatal("expected errTestDelegateState, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := b.States(); err != errTestDelegateStates {
|
if _, err := b.Workspaces(); err != errTestDelegateStates {
|
||||||
t.Fatal("expected errTestDelegateStates, got:", err)
|
t.Fatal("expected errTestDelegateStates, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.DeleteState("test"); err != errTestDelegateDeleteState {
|
if err := b.DeleteWorkspace("test"); err != errTestDelegateDeleteState {
|
||||||
t.Fatal("expected errTestDelegateDeleteState, got:", err)
|
t.Fatal("expected errTestDelegateDeleteState, got:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,12 +23,14 @@ type CountHook struct {
|
||||||
ToRemove int
|
ToRemove int
|
||||||
ToRemoveAndAdd int
|
ToRemoveAndAdd int
|
||||||
|
|
||||||
pending map[string]countHookAction
|
pending map[string]plans.Action
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
terraform.NilHook
|
terraform.NilHook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ terraform.Hook = (*CountHook)(nil)
|
||||||
|
|
||||||
func (h *CountHook) Reset() {
|
func (h *CountHook) Reset() {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
@ -35,52 +41,39 @@ func (h *CountHook) Reset() {
|
||||||
h.Removed = 0
|
h.Removed = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *CountHook) PreApply(
|
func (h *CountHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
d *terraform.InstanceDiff) (terraform.HookAction, error) {
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
if d.Empty() {
|
|
||||||
return terraform.HookActionContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.pending == nil {
|
if h.pending == nil {
|
||||||
h.pending = make(map[string]countHookAction)
|
h.pending = make(map[string]plans.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
action := countHookActionChange
|
h.pending[addr.String()] = action
|
||||||
if d.GetDestroy() {
|
|
||||||
action = countHookActionRemove
|
|
||||||
} else if s.ID == "" {
|
|
||||||
action = countHookActionAdd
|
|
||||||
}
|
|
||||||
|
|
||||||
h.pending[n.HumanId()] = action
|
|
||||||
|
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *CountHook) PostApply(
|
func (h *CountHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, err error) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
e error) (terraform.HookAction, error) {
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
if h.pending != nil {
|
if h.pending != nil {
|
||||||
if a, ok := h.pending[n.HumanId()]; ok {
|
pendingKey := addr.String()
|
||||||
delete(h.pending, n.HumanId())
|
if action, ok := h.pending[pendingKey]; ok {
|
||||||
|
delete(h.pending, pendingKey)
|
||||||
|
|
||||||
if e == nil {
|
if err == nil {
|
||||||
switch a {
|
switch action {
|
||||||
case countHookActionAdd:
|
case plans.Replace:
|
||||||
h.Added += 1
|
h.Added++
|
||||||
case countHookActionChange:
|
h.Removed++
|
||||||
h.Changed += 1
|
case plans.Create:
|
||||||
case countHookActionRemove:
|
h.Added++
|
||||||
h.Removed += 1
|
case plans.Delete:
|
||||||
|
h.Changed++
|
||||||
|
case plans.Update:
|
||||||
|
h.Removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,25 +82,23 @@ func (h *CountHook) PostApply(
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *CountHook) PostDiff(
|
func (h *CountHook) PostDiff(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo, d *terraform.InstanceDiff) (
|
|
||||||
terraform.HookAction, error) {
|
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
// We don't count anything for data sources
|
// We don't count anything for data resources
|
||||||
if strings.HasPrefix(n.Id, "data.") {
|
if addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch d.ChangeType() {
|
switch action {
|
||||||
case terraform.DiffDestroyCreate:
|
case plans.Replace:
|
||||||
h.ToRemoveAndAdd += 1
|
h.ToRemoveAndAdd += 1
|
||||||
case terraform.DiffCreate:
|
case plans.Create:
|
||||||
h.ToAdd += 1
|
h.ToAdd += 1
|
||||||
case terraform.DiffDestroy:
|
case plans.Delete:
|
||||||
h.ToRemove += 1
|
h.ToRemove += 1
|
||||||
case terraform.DiffUpdate:
|
case plans.Update:
|
||||||
h.ToChange += 1
|
h.ToChange += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,11 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,10 +23,14 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
|
||||||
"lorem": &terraform.InstanceDiff{DestroyDeposed: true},
|
"lorem": &terraform.InstanceDiff{DestroyDeposed: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{} // TODO
|
for k := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.DeposedKey("deadbeef"), plans.Delete, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -31,8 +40,7 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
|
||||||
expected.ToRemove = 1
|
expected.ToRemove = 1
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, h) {
|
if !reflect.DeepEqual(expected, h) {
|
||||||
t.Fatalf("Expected %#v, got %#v instead.",
|
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
|
||||||
expected, h)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,10 +54,14 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
|
||||||
"ipsum": &terraform.InstanceDiff{Destroy: true},
|
"ipsum": &terraform.InstanceDiff{Destroy: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{} // TODO
|
for k := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.CurrentGen, plans.Delete, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -59,8 +71,7 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
|
||||||
expected.ToRemove = 4
|
expected.ToRemove = 4
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, h) {
|
if !reflect.DeepEqual(expected, h) {
|
||||||
t.Fatalf("Expected %#v, got %#v instead.",
|
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
|
||||||
expected, h)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,10 +96,14 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{}
|
for k := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.CurrentGen, plans.Create, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -98,8 +113,7 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
|
||||||
expected.ToRemove = 0
|
expected.ToRemove = 0
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, h) {
|
if !reflect.DeepEqual(expected, h) {
|
||||||
t.Fatalf("Expected %#v, got %#v instead.",
|
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
|
||||||
expected, h)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,10 +141,14 @@ func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{}
|
for k := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.CurrentGen, plans.Update, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -140,32 +158,28 @@ func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
|
||||||
expected.ToRemove = 0
|
expected.ToRemove = 0
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, h) {
|
if !reflect.DeepEqual(expected, h) {
|
||||||
t.Fatalf("Expected %#v, got %#v instead.",
|
t.Fatalf("Expected %#v, got %#v instead.", expected, h)
|
||||||
expected, h)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCountHookPostDiff_Mixed(t *testing.T) {
|
func TestCountHookPostDiff_Mixed(t *testing.T) {
|
||||||
h := new(CountHook)
|
h := new(CountHook)
|
||||||
|
|
||||||
resources := map[string]*terraform.InstanceDiff{
|
resources := map[string]plans.Action{
|
||||||
"foo": &terraform.InstanceDiff{
|
"foo": plans.Delete,
|
||||||
Destroy: true,
|
"bar": plans.NoOp,
|
||||||
},
|
"lorem": plans.Update,
|
||||||
"bar": &terraform.InstanceDiff{},
|
"ipsum": plans.Delete,
|
||||||
"lorem": &terraform.InstanceDiff{
|
|
||||||
Destroy: false,
|
|
||||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
||||||
"foo": &terraform.ResourceAttrDiff{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ipsum": &terraform.InstanceDiff{Destroy: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{}
|
for k, a := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.CurrentGen, a, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -190,10 +204,14 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
|
||||||
"ipsum": &terraform.InstanceDiff{},
|
"ipsum": &terraform.InstanceDiff{},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{}
|
for k := range resources {
|
||||||
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
for _, d := range resources {
|
h.PostDiff(addr, states.CurrentGen, plans.NoOp, cty.DynamicVal, cty.DynamicVal)
|
||||||
h.PostDiff(n, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
@ -211,23 +229,21 @@ func TestCountHookPostDiff_NoChange(t *testing.T) {
|
||||||
func TestCountHookPostDiff_DataSource(t *testing.T) {
|
func TestCountHookPostDiff_DataSource(t *testing.T) {
|
||||||
h := new(CountHook)
|
h := new(CountHook)
|
||||||
|
|
||||||
resources := map[string]*terraform.InstanceDiff{
|
resources := map[string]plans.Action{
|
||||||
"data.foo": &terraform.InstanceDiff{
|
"foo": plans.Delete,
|
||||||
Destroy: true,
|
"bar": plans.NoOp,
|
||||||
},
|
"lorem": plans.Update,
|
||||||
"data.bar": &terraform.InstanceDiff{},
|
"ipsum": plans.Delete,
|
||||||
"data.lorem": &terraform.InstanceDiff{
|
|
||||||
Destroy: false,
|
|
||||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
||||||
"foo": &terraform.ResourceAttrDiff{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"data.ipsum": &terraform.InstanceDiff{Destroy: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, d := range resources {
|
for k, a := range resources {
|
||||||
n := &terraform.InstanceInfo{Id: k}
|
addr := addrs.Resource{
|
||||||
h.PostDiff(n, d)
|
Mode: addrs.DataResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: k,
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
|
h.PostDiff(addr, states.CurrentGen, a, cty.DynamicVal, cty.DynamicVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := new(CountHook)
|
expected := new(CountHook)
|
||||||
|
|
|
@ -3,7 +3,8 @@ package local
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,21 +14,20 @@ type StateHook struct {
|
||||||
terraform.NilHook
|
terraform.NilHook
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
State state.State
|
StateMgr statemgr.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *StateHook) PostStateUpdate(
|
var _ terraform.Hook = (*StateHook)(nil)
|
||||||
s *terraform.State) (terraform.HookAction, error) {
|
|
||||||
|
func (h *StateHook) PostStateUpdate(new *states.State) (terraform.HookAction, error) {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
if h.State != nil {
|
if h.StateMgr != nil {
|
||||||
// Write the new state
|
if err := h.StateMgr.WriteState(new); err != nil {
|
||||||
if err := h.State.WriteState(s); err != nil {
|
|
||||||
return terraform.HookActionHalt, err
|
return terraform.HookActionHalt, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue forth
|
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,8 +13,8 @@ func TestStateHook_impl(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateHook(t *testing.T) {
|
func TestStateHook(t *testing.T) {
|
||||||
is := &state.InmemState{}
|
is := statemgr.NewTransientInMemory(nil)
|
||||||
var hook terraform.Hook = &StateHook{State: is}
|
var hook terraform.Hook = &StateHook{StateMgr: is}
|
||||||
|
|
||||||
s := state.TestStateInitial()
|
s := state.TestStateInitial()
|
||||||
action, err := hook.PostStateUpdate(s)
|
action, err := hook.PostStateUpdate(s)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
@ -97,12 +97,12 @@ type TestLocalSingleState struct {
|
||||||
*Local
|
*Local
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestLocalSingleState) State(name string) (state.State, error) {
|
func (b *TestLocalSingleState) State(name string) (statemgr.Full, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.Local.State(name)
|
return b.Local.StateMgr(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestLocalSingleState) States() ([]string, error) {
|
func (b *TestLocalSingleState) States() ([]string, error) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -25,15 +25,16 @@ func (Nil) Configure(cty.Value) tfdiags.Diagnostics {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Nil) State(string) (state.State, error) {
|
func (Nil) StateMgr(string) (statemgr.Full, error) {
|
||||||
// We have to return a non-nil state to adhere to the interface
|
// We must return a non-nil manager to adhere to the interface, so
|
||||||
return &state.InmemState{}, nil
|
// we'll return an in-memory-only one.
|
||||||
|
return statemgr.NewFullFake(statemgr.NewTransientInMemory(nil), nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Nil) DeleteState(string) error {
|
func (Nil) DeleteWorkspace(string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Nil) States() ([]string, error) {
|
func (Nil) Workspaces() ([]string, error) {
|
||||||
return []string{DefaultStateName}, nil
|
return []string{DefaultStateName}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,15 +82,15 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(string) error {
|
func (b *Backend) DeleteWorkspace(string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/Azure/go-autorest/autorest"
|
"github.com/Azure/go-autorest/autorest"
|
||||||
"github.com/Azure/go-autorest/autorest/adal"
|
"github.com/Azure/go-autorest/autorest/adal"
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
"github.com/Azure/go-autorest/autorest/azure"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/storage"
|
"github.com/Azure/azure-sdk-for-go/storage"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -18,7 +19,7 @@ const (
|
||||||
keyEnvPrefix = "env:"
|
keyEnvPrefix = "env:"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
prefix := b.keyName + keyEnvPrefix
|
prefix := b.keyName + keyEnvPrefix
|
||||||
params := storage.ListBlobsParameters{
|
params := storage.ListBlobsParameters{
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
|
@ -52,7 +53,7 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName || name == "" {
|
if name == backend.DefaultStateName || name == "" {
|
||||||
return fmt.Errorf("can't delete default state")
|
return fmt.Errorf("can't delete default state")
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,7 @@ func (b *Backend) DeleteState(name string) error {
|
||||||
return blobReference.Delete(options)
|
return blobReference.Delete(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
client := &RemoteClient{
|
client := &RemoteClient{
|
||||||
blobClient: b.blobClient,
|
blobClient: b.blobClient,
|
||||||
containerName: b.containerName,
|
containerName: b.containerName,
|
||||||
|
@ -100,7 +101,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
|
||||||
// If we have no state, we have to create an empty state
|
// If we have no state, we have to create an empty state
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
err = lockUnlock(err)
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,19 @@ package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"encoding/base64"
|
|
||||||
"github.com/Azure/azure-sdk-for-go/storage"
|
"github.com/Azure/azure-sdk-for-go/storage"
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -162,7 +163,7 @@ func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
||||||
log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state")
|
log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state")
|
||||||
|
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
return "", fmt.Errorf("Failed to write empty state for locking: %s", err)
|
return "", fmt.Errorf("Failed to write empty state for locking: %s", err)
|
||||||
}
|
}
|
||||||
if err := stateMgr.PersistState(); err != nil {
|
if err := stateMgr.PersistState(); err != nil {
|
||||||
|
|
|
@ -6,12 +6,13 @@ package remotestate
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/state"
|
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backend implements backend.Backend for remote state backends.
|
// Backend implements backend.Backend for remote state backends.
|
||||||
|
@ -48,15 +49,15 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||||
return b.Backend.Configure(obj)
|
return b.Backend.Configure(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
if b.client == nil {
|
if b.client == nil {
|
||||||
panic("nil remote client")
|
panic("nil remote client")
|
||||||
|
|
|
@ -7,14 +7,15 @@ import (
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
keyEnvPrefix = "-env:"
|
keyEnvPrefix = "-env:"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
// List our raw path
|
// List our raw path
|
||||||
prefix := b.configData.Get("path").(string) + keyEnvPrefix
|
prefix := b.configData.Get("path").(string) + keyEnvPrefix
|
||||||
keys, _, err := b.client.KV().Keys(prefix, "/", nil)
|
keys, _, err := b.client.KV().Keys(prefix, "/", nil)
|
||||||
|
@ -49,7 +50,7 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName || name == "" {
|
if name == backend.DefaultStateName || name == "" {
|
||||||
return fmt.Errorf("can't delete default state")
|
return fmt.Errorf("can't delete default state")
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,7 @@ func (b *Backend) DeleteState(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
||||||
// Determine the path of the data
|
// Determine the path of the data
|
||||||
path := b.path(name)
|
path := b.path(name)
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
gzip := b.configData.Get("gzip").(bool)
|
gzip := b.configData.Get("gzip").(bool)
|
||||||
|
|
||||||
// Build the state client
|
// Build the state client
|
||||||
var stateMgr state.State = &remote.State{
|
var stateMgr = &remote.State{
|
||||||
Client: &RemoteClient{
|
Client: &RemoteClient{
|
||||||
Client: b.client,
|
Client: b.client,
|
||||||
Path: path,
|
Path: path,
|
||||||
|
@ -80,9 +81,8 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not locking, disable it
|
|
||||||
if !b.lock {
|
if !b.lock {
|
||||||
stateMgr = &state.LockDisabled{Inner: stateMgr}
|
stateMgr.DisableLocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// the default state always exists
|
// the default state always exists
|
||||||
|
@ -117,7 +117,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
|
||||||
// If we have no state, we have to create an empty state
|
// If we have no state, we have to create an empty state
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
err = lockUnlock(err)
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,15 +75,15 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(string) error {
|
func (b *Backend) DeleteWorkspace(string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
etcdv3 "github.com/coreos/etcd/clientv3"
|
etcdv3 "github.com/coreos/etcd/clientv3"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly())
|
res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -29,7 +30,7 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName || name == "" {
|
if name == backend.DefaultStateName || name == "" {
|
||||||
return fmt.Errorf("Can't delete default state.")
|
return fmt.Errorf("Can't delete default state.")
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ func (b *Backend) DeleteState(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
var stateMgr state.State = &remote.State{
|
var stateMgr state.State = &remote.State{
|
||||||
Client: &RemoteClient{
|
Client: &RemoteClient{
|
||||||
Client: b.client,
|
Client: b.client,
|
||||||
|
@ -73,7 +74,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
err = lockUnlock(err)
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"cloud.google.com/go/storage"
|
"cloud.google.com/go/storage"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
"google.golang.org/api/iterator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -19,9 +20,9 @@ const (
|
||||||
lockFileSuffix = ".tflock"
|
lockFileSuffix = ".tflock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// States returns a list of names for the states found on GCS. The default
|
// Workspaces returns a list of names for the workspaces found on GCS. The default
|
||||||
// state is always returned as the first element in the slice.
|
// state is always returned as the first element in the slice.
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
states := []string{backend.DefaultStateName}
|
states := []string{backend.DefaultStateName}
|
||||||
|
|
||||||
bucket := b.storageClient.Bucket(b.bucketName)
|
bucket := b.storageClient.Bucket(b.bucketName)
|
||||||
|
@ -53,8 +54,8 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return states, nil
|
return states, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteState deletes the named state. The "default" state cannot be deleted.
|
// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName {
|
if name == backend.DefaultStateName {
|
||||||
return fmt.Errorf("cowardly refusing to delete the %q state", name)
|
return fmt.Errorf("cowardly refusing to delete the %q state", name)
|
||||||
}
|
}
|
||||||
|
@ -83,9 +84,9 @@ func (b *Backend) client(name string) (*remoteClient, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// State reads and returns the named state from GCS. If the named state does
|
// StateMgr reads and returns the named state from GCS. If the named state does
|
||||||
// not yet exist, a new state file is created.
|
// not yet exist, a new state file is created.
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
c, err := b.client(name)
|
c, err := b.client(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -127,7 +128,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
return baseErr
|
return baseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := st.WriteState(terraform.NewState()); err != nil {
|
if err := st.WriteState(states.NewState()); err != nil {
|
||||||
return nil, unlock(err)
|
return nil, unlock(err)
|
||||||
}
|
}
|
||||||
if err := st.PersistState(); err != nil {
|
if err := st.PersistState(); err != nil {
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
@ -157,10 +157,10 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
return &remote.State{Client: b.client}, nil
|
return &remote.State{Client: b.client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(string) error {
|
func (b *Backend) DeleteWorkspace(string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
statespkg "github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
// we keep the states and locks in package-level variables, so that they can be
|
// we keep the states and locks in package-level variables, so that they can be
|
||||||
|
@ -87,7 +87,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
states.Lock()
|
states.Lock()
|
||||||
defer states.Unlock()
|
defer states.Unlock()
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return workspaces, nil
|
return workspaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
states.Lock()
|
states.Lock()
|
||||||
defer states.Unlock()
|
defer states.Unlock()
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func (b *Backend) DeleteState(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
states.Lock()
|
states.Lock()
|
||||||
defer states.Unlock()
|
defer states.Unlock()
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
|
||||||
// If we have no state, we have to create an empty state
|
// If we have no state, we have to create an empty state
|
||||||
if v := s.State(); v == nil {
|
if v := s.State(); v == nil {
|
||||||
if err := s.WriteState(terraform.NewState()); err != nil {
|
if err := s.WriteState(statespkg.NewState()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.PersistState(); err != nil {
|
if err := s.PersistState(); err != nil {
|
||||||
|
|
|
@ -8,15 +8,16 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
tritonErrors "github.com/joyent/triton-go/errors"
|
||||||
|
"github.com/joyent/triton-go/storage"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
tritonErrors "github.com/joyent/triton-go/errors"
|
|
||||||
"github.com/joyent/triton-go/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
result := []string{backend.DefaultStateName}
|
result := []string{backend.DefaultStateName}
|
||||||
|
|
||||||
objs, err := b.storageClient.Dir().List(context.Background(), &storage.ListDirectoryInput{
|
objs, err := b.storageClient.Dir().List(context.Background(), &storage.ListDirectoryInput{
|
||||||
|
@ -39,7 +40,7 @@ func (b *Backend) States() ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName || name == "" {
|
if name == backend.DefaultStateName || name == "" {
|
||||||
return fmt.Errorf("can't delete default state")
|
return fmt.Errorf("can't delete default state")
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,7 @@ func (b *Backend) DeleteState(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, errors.New("missing state name")
|
return nil, errors.New("missing state name")
|
||||||
}
|
}
|
||||||
|
@ -103,7 +104,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
|
||||||
// If we have no state, we have to create an empty state
|
// If we have no state, we have to create an empty state
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
err = lockUnlock(err)
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@ import (
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
prefix := b.workspaceKeyPrefix + "/"
|
prefix := b.workspaceKeyPrefix + "/"
|
||||||
|
|
||||||
// List bucket root if there is no workspaceKeyPrefix
|
// List bucket root if there is no workspaceKeyPrefix
|
||||||
|
@ -78,7 +79,7 @@ func (b *Backend) keyEnv(key string) string {
|
||||||
return parts[1]
|
return parts[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
if name == backend.DefaultStateName || name == "" {
|
if name == backend.DefaultStateName || name == "" {
|
||||||
return fmt.Errorf("can't delete default state")
|
return fmt.Errorf("can't delete default state")
|
||||||
}
|
}
|
||||||
|
@ -111,7 +112,7 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
client, err := b.remoteClient(name)
|
client, err := b.remoteClient(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -126,7 +127,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
// If we need to force-unlock, but for some reason the state no longer
|
// If we need to force-unlock, but for some reason the state no longer
|
||||||
// exists, the user will have to use aws tools to manually fix the
|
// exists, the user will have to use aws tools to manually fix the
|
||||||
// situation.
|
// situation.
|
||||||
existing, err := b.States()
|
existing, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -167,7 +168,7 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
|
||||||
// If we have no state, we have to create an empty state
|
// If we have no state, we have to create an empty state
|
||||||
if v := stateMgr.State(); v == nil {
|
if v := stateMgr.State(); v == nil {
|
||||||
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||||
err = lockUnlock(err)
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,15 @@ import (
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Backend) States() ([]string, error) {
|
func (b *Backend) Workspaces() ([]string, error) {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) DeleteState(name string) error {
|
func (b *Backend) DeleteWorkspace(name string) error {
|
||||||
return backend.ErrNamedStatesNotSupported
|
return backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) State(name string) (state.State, error) {
|
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
return nil, backend.ErrNamedStatesNotSupported
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,17 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
|
||||||
|
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/hashicorp/hcl2/hcldec"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||||
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestBackendConfig validates and configures the backend with the
|
// TestBackendConfig validates and configures the backend with the
|
||||||
|
@ -67,31 +66,19 @@ func TestWrapConfig(raw map[string]interface{}) hcl.Body {
|
||||||
func TestBackendStates(t *testing.T, b Backend) {
|
func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
noDefault := false
|
workspaces, err := b.Workspaces()
|
||||||
if _, err := b.State(DefaultStateName); err != nil {
|
if err == ErrNamedStatesNotSupported {
|
||||||
if err == ErrDefaultStateNotSupported {
|
t.Logf("TestBackend: workspaces not supported in %T, skipping", b)
|
||||||
noDefault = true
|
return
|
||||||
} else {
|
|
||||||
t.Fatalf("error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
states, err := b.States()
|
|
||||||
if err != nil {
|
|
||||||
if err == ErrNamedStatesNotSupported {
|
|
||||||
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("error: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test it starts with only the default
|
// Test it starts with only the default
|
||||||
if !noDefault && (len(states) != 1 || states[0] != DefaultStateName) {
|
if len(workspaces) != 1 || workspaces[0] != DefaultStateName {
|
||||||
t.Fatalf("should have default to start: %#v", states)
|
t.Fatalf("should only have default to start: %#v", workspaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a couple states
|
// Create a couple states
|
||||||
foo, err := b.State("foo")
|
foo, err := b.StateMgr("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +89,7 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatalf("should be empty: %s", v)
|
t.Fatalf("should be empty: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
bar, err := b.State("bar")
|
bar, err := b.StateMgr("bar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -115,24 +102,10 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
|
|
||||||
// Verify they are distinct states that can be read back from storage
|
// Verify they are distinct states that can be read back from storage
|
||||||
{
|
{
|
||||||
// start with a fresh state, and record the lineage being
|
// We'll use two distinct states here and verify that changing one
|
||||||
// written to "bar"
|
// does not also change the other.
|
||||||
barState := terraform.NewState()
|
barState := states.NewState()
|
||||||
|
fooState := states.NewState()
|
||||||
// creating the named state may have created a lineage, so use that if it exists.
|
|
||||||
if s := bar.State(); s != nil && s.Lineage != "" {
|
|
||||||
barState.Lineage = s.Lineage
|
|
||||||
}
|
|
||||||
barLineage := barState.Lineage
|
|
||||||
|
|
||||||
// the foo lineage should be distinct from bar, and unchanged after
|
|
||||||
// modifying bar
|
|
||||||
fooState := terraform.NewState()
|
|
||||||
// creating the named state may have created a lineage, so use that if it exists.
|
|
||||||
if s := foo.State(); s != nil && s.Lineage != "" {
|
|
||||||
fooState.Lineage = s.Lineage
|
|
||||||
}
|
|
||||||
fooLineage := fooState.Lineage
|
|
||||||
|
|
||||||
// write a known state to foo
|
// write a known state to foo
|
||||||
if err := foo.WriteState(fooState); err != nil {
|
if err := foo.WriteState(fooState); err != nil {
|
||||||
|
@ -142,6 +115,25 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatal("error persisting foo state:", err)
|
t.Fatal("error persisting foo state:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll make "bar" different by adding a fake resource state to it.
|
||||||
|
barState.SyncWrapper().SetResourceInstanceCurrent(
|
||||||
|
addrs.ResourceInstance{
|
||||||
|
Resource: addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_thing",
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte("{}"),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
SchemaVersion: 0,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{
|
||||||
|
Type: "test",
|
||||||
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
|
||||||
// write a distinct known state to bar
|
// write a distinct known state to bar
|
||||||
if err := bar.WriteState(barState); err != nil {
|
if err := bar.WriteState(barState); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
|
@ -155,17 +147,12 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatal("error refreshing foo:", err)
|
t.Fatal("error refreshing foo:", err)
|
||||||
}
|
}
|
||||||
fooState = foo.State()
|
fooState = foo.State()
|
||||||
switch {
|
if fooState.HasResources() {
|
||||||
case fooState == nil:
|
t.Fatal("after writing a resource to bar, foo now has resources too")
|
||||||
t.Fatal("nil state read from foo")
|
|
||||||
case fooState.Lineage == barLineage:
|
|
||||||
t.Fatalf("bar lineage read from foo: %#v", fooState)
|
|
||||||
case fooState.Lineage != fooLineage:
|
|
||||||
t.Fatal("foo lineage alterred")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch foo again from the backend
|
// fetch foo again from the backend
|
||||||
foo, err = b.State("foo")
|
foo, err = b.StateMgr("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("error re-fetching state:", err)
|
t.Fatal("error re-fetching state:", err)
|
||||||
}
|
}
|
||||||
|
@ -173,15 +160,12 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatal("error refreshing foo:", err)
|
t.Fatal("error refreshing foo:", err)
|
||||||
}
|
}
|
||||||
fooState = foo.State()
|
fooState = foo.State()
|
||||||
switch {
|
if fooState.HasResources() {
|
||||||
case fooState == nil:
|
t.Fatal("after writing a resource to bar and re-reading foo, foo now has resources too")
|
||||||
t.Fatal("nil state read from foo")
|
|
||||||
case fooState.Lineage != fooLineage:
|
|
||||||
t.Fatal("incorrect state returned from backend")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the bar again from the backend
|
// fetch the bar again from the backend
|
||||||
bar, err = b.State("bar")
|
bar, err = b.StateMgr("bar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("error re-fetching state:", err)
|
t.Fatal("error re-fetching state:", err)
|
||||||
}
|
}
|
||||||
|
@ -189,46 +173,40 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatal("error refreshing bar:", err)
|
t.Fatal("error refreshing bar:", err)
|
||||||
}
|
}
|
||||||
barState = bar.State()
|
barState = bar.State()
|
||||||
switch {
|
if !barState.HasResources() {
|
||||||
case barState == nil:
|
t.Fatal("after writing a resource instance object to bar and re-reading it, the object has vanished")
|
||||||
t.Fatal("nil state read from bar")
|
|
||||||
case barState.Lineage != barLineage:
|
|
||||||
t.Fatal("incorrect state returned from backend")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we can now list them
|
// Verify we can now list them
|
||||||
{
|
{
|
||||||
// we determined that named stated are supported earlier
|
// we determined that named stated are supported earlier
|
||||||
states, err := b.States()
|
workspaces, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(states)
|
sort.Strings(workspaces)
|
||||||
expected := []string{"bar", "default", "foo"}
|
expected := []string{"bar", "default", "foo"}
|
||||||
if noDefault {
|
if !reflect.DeepEqual(workspaces, expected) {
|
||||||
expected = []string{"bar", "foo"}
|
t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected)
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(states, expected) {
|
|
||||||
t.Fatalf("bad: %#v", states)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete some states
|
// Delete some workspaces
|
||||||
if err := b.DeleteState("foo"); err != nil {
|
if err := b.DeleteWorkspace("foo"); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the default state can't be deleted
|
// Verify the default state can't be deleted
|
||||||
if err := b.DeleteState(DefaultStateName); err == nil {
|
if err := b.DeleteWorkspace(DefaultStateName); err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and delete the foo state again.
|
// Create and delete the foo workspace again.
|
||||||
// Make sure that there are no leftover artifacts from a deleted state
|
// Make sure that there are no leftover artifacts from a deleted state
|
||||||
// preventing re-creation.
|
// preventing re-creation.
|
||||||
foo, err = b.State("foo")
|
foo, err = b.StateMgr("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -239,23 +217,20 @@ func TestBackendStates(t *testing.T, b Backend) {
|
||||||
t.Fatalf("should be empty: %s", v)
|
t.Fatalf("should be empty: %s", v)
|
||||||
}
|
}
|
||||||
// and delete it again
|
// and delete it again
|
||||||
if err := b.DeleteState("foo"); err != nil {
|
if err := b.DeleteWorkspace("foo"); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify deletion
|
// Verify deletion
|
||||||
{
|
{
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err == ErrNamedStatesNotSupported {
|
if err == ErrWorkspacesNotSupported {
|
||||||
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(states)
|
sort.Strings(states)
|
||||||
expected := []string{"bar", "default"}
|
expected := []string{"bar", "default"}
|
||||||
if noDefault {
|
|
||||||
expected = []string{"bar"}
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(states, expected) {
|
if !reflect.DeepEqual(states, expected) {
|
||||||
t.Fatalf("bad: %#v", states)
|
t.Fatalf("bad: %#v", states)
|
||||||
}
|
}
|
||||||
|
@ -282,7 +257,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// Get the default state for each
|
// Get the default state for each
|
||||||
b1StateMgr, err := b1.State(DefaultStateName)
|
b1StateMgr, err := b1.StateMgr(DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -298,7 +273,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
|
||||||
|
|
||||||
t.Logf("TestBackend: testing state locking for %T", b1)
|
t.Logf("TestBackend: testing state locking for %T", b1)
|
||||||
|
|
||||||
b2StateMgr, err := b2.State(DefaultStateName)
|
b2StateMgr, err := b2.StateMgr(DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -326,7 +301,7 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
|
||||||
// Make sure we can still get the state.State from another instance even
|
// Make sure we can still get the state.State from another instance even
|
||||||
// when locked. This should only happen when a state is loaded via the
|
// when locked. This should only happen when a state is loaded via the
|
||||||
// backend, and as a remote state.
|
// backend, and as a remote state.
|
||||||
_, err = b2.State(DefaultStateName)
|
_, err = b2.StateMgr(DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to read locked state from another backend instance: %s", err)
|
t.Errorf("failed to read locked state from another backend instance: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -389,10 +364,10 @@ func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
|
||||||
t.Fatal("client B obtained lock while held by client A")
|
t.Fatal("client B obtained lock while held by client A")
|
||||||
}
|
}
|
||||||
|
|
||||||
infoErr, ok := err.(*state.LockError)
|
infoErr, ok := err.(*statemgr.LockError)
|
||||||
if !ok {
|
if !ok {
|
||||||
unlock()
|
unlock()
|
||||||
t.Fatalf("expected type *state.LockError, got : %#v", err)
|
t.Fatalf("expected type *statemgr.LockError, got : %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to unlock with the second unlocker, using the ID from the error
|
// try to unlock with the second unlocker, using the ID from the error
|
||||||
|
|
|
@ -4,13 +4,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func dataSourceRemoteStateGetSchema() providers.Schema {
|
func dataSourceRemoteStateGetSchema() providers.Schema {
|
||||||
|
@ -109,7 +109,7 @@ func dataSourceRemoteStateRead(d *cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := b.State(name)
|
state, err := b.StateMgr(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(tfdiags.AttributeValue(
|
diags = diags.Append(tfdiags.AttributeValue(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
@ -137,11 +137,10 @@ func dataSourceRemoteStateRead(d *cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteState := state.State()
|
remoteState := state.State()
|
||||||
if remoteState.Empty() {
|
mod := remoteState.RootModule()
|
||||||
log.Println("[DEBUG] empty remote state")
|
if mod != nil { // should always have a root module in any valid state
|
||||||
} else {
|
for k, os := range mod.OutputValues {
|
||||||
for k, os := range remoteState.RootModule().Outputs {
|
outputs[k] = os.Value
|
||||||
outputs[k] = hcl2shim.HCL2ValueFromConfigValue(os.Value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,8 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/config"
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,8 +44,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve")
|
cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve")
|
||||||
}
|
}
|
||||||
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
|
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
|
||||||
cmdFlags.IntVar(
|
cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
|
||||||
&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
|
|
||||||
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
|
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
|
||||||
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
|
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
|
||||||
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
|
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
|
||||||
|
@ -84,8 +82,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Do a detect to determine if we need to do an init + apply.
|
// Do a detect to determine if we need to do an init + apply.
|
||||||
if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
|
if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf("Invalid path: %s", err))
|
||||||
"Invalid path: %s", err))
|
|
||||||
return 1
|
return 1
|
||||||
} else if !strings.HasPrefix(detected, "file") {
|
} else if !strings.HasPrefix(detected, "file") {
|
||||||
// If this isn't a file URL then we're doing an init +
|
// If this isn't a file URL then we're doing an init +
|
||||||
|
@ -102,39 +99,47 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is a plan
|
// Check if the path is a plan
|
||||||
plan, err := c.Plan(configPath)
|
planFile, err := c.PlanFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if c.Destroy && plan != nil {
|
if c.Destroy && planFile != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf("Destroy can't be called with a plan file."))
|
||||||
"Destroy can't be called with a plan file."))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if plan != nil {
|
if planFile != nil {
|
||||||
// Reset the config path for backend loading
|
// Reset the config path for backend loading
|
||||||
configPath = ""
|
configPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
var backendConfig *configs.Backend
|
// Load the backend
|
||||||
if plan == nil {
|
var be backend.Enhanced
|
||||||
var configDiags tfdiags.Diagnostics
|
var beDiags tfdiags.Diagnostics
|
||||||
backendConfig, configDiags = c.loadBackendConfig(configPath)
|
if planFile == nil {
|
||||||
|
backendConfig, configDiags := c.loadBackendConfig(configPath)
|
||||||
diags = diags.Append(configDiags)
|
diags = diags.Append(configDiags)
|
||||||
if configDiags.HasErrors() {
|
if configDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Load the backend
|
be, beDiags = c.Backend(&BackendOpts{
|
||||||
b, beDiags := c.Backend(&BackendOpts{
|
Config: backendConfig,
|
||||||
Config: backendConfig,
|
})
|
||||||
Plan: plan,
|
} else {
|
||||||
})
|
plan, err := planFile.ReadPlan()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to read plan from plan file",
|
||||||
|
fmt.Sprintf("Cannot read the plan from the given plan file: %s.", err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
be, beDiags = c.BackendForPlan(plan.Backend)
|
||||||
|
}
|
||||||
diags = diags.Append(beDiags)
|
diags = diags.Append(beDiags)
|
||||||
if beDiags.HasErrors() {
|
if beDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
|
@ -148,11 +153,11 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
diags = nil
|
diags = nil
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(be)
|
||||||
opReq.AutoApprove = autoApprove
|
opReq.AutoApprove = autoApprove
|
||||||
opReq.Destroy = c.Destroy
|
opReq.Destroy = c.Destroy
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.Plan = plan
|
opReq.PlanFile = planFile
|
||||||
opReq.PlanRefresh = refresh
|
opReq.PlanRefresh = refresh
|
||||||
opReq.Type = backend.OperationTypeApply
|
opReq.Type = backend.OperationTypeApply
|
||||||
opReq.AutoApprove = autoApprove
|
opReq.AutoApprove = autoApprove
|
||||||
|
@ -163,7 +168,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
op, err := c.RunOperation(b, opReq)
|
op, err := c.RunOperation(be, opReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.showDiagnostics(err)
|
c.showDiagnostics(err)
|
||||||
return 1
|
return 1
|
||||||
|
@ -314,26 +319,19 @@ Options:
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputsAsString(state *terraform.State, modPath addrs.ModuleInstance, schema []*config.Output, includeHeader bool) string {
|
func outputsAsString(state *states.State, modPath addrs.ModuleInstance, schema map[string]*configs.Output, includeHeader bool) string {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ms := state.ModuleByPath(modPath)
|
ms := state.Module(modPath)
|
||||||
if ms == nil {
|
if ms == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs := ms.Outputs
|
outputs := ms.OutputValues
|
||||||
outputBuf := new(bytes.Buffer)
|
outputBuf := new(bytes.Buffer)
|
||||||
if len(outputs) > 0 {
|
if len(outputs) > 0 {
|
||||||
schemaMap := make(map[string]*config.Output)
|
|
||||||
if schema != nil {
|
|
||||||
for _, s := range schema {
|
|
||||||
schemaMap[s.Name] = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeHeader {
|
if includeHeader {
|
||||||
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
|
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
|
||||||
}
|
}
|
||||||
|
@ -350,23 +348,14 @@ func outputsAsString(state *terraform.State, modPath addrs.ModuleInstance, schem
|
||||||
sort.Strings(ks)
|
sort.Strings(ks)
|
||||||
|
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
schema, ok := schemaMap[k]
|
schema, ok := schema[k]
|
||||||
if ok && schema.Sensitive {
|
if ok && schema.Sensitive {
|
||||||
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
|
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v := outputs[k]
|
//v := outputs[k]
|
||||||
switch typedV := v.Value.(type) {
|
outputBuf.WriteString("output printer not yet updated to use the same value formatter as 'terraform console'")
|
||||||
case string:
|
|
||||||
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
|
|
||||||
case []interface{}:
|
|
||||||
outputBuf.WriteString(formatListOutput("", k, typedV))
|
|
||||||
outputBuf.WriteString("\n")
|
|
||||||
case map[string]interface{}:
|
|
||||||
outputBuf.WriteString(formatMapOutput("", k, typedV))
|
|
||||||
outputBuf.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,29 +5,30 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApply_destroy(t *testing.T) {
|
func TestApply_destroy(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -98,22 +99,20 @@ func TestApply_destroy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_destroyLockedState(t *testing.T) {
|
func TestApply_destroyLockedState(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState("./testdata", statePath)
|
||||||
|
@ -150,9 +149,7 @@ func TestApply_destroyLockedState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_destroyPlan(t *testing.T) {
|
func TestApply_destroyPlan(t *testing.T) {
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
})
|
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -174,28 +171,32 @@ func TestApply_destroyPlan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_destroyTargeted(t *testing.T) {
|
func TestApply_destroyTargeted(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "i-ab123",
|
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
"test_load_balancer.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_load_balancer",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "lb-abc123",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_load_balancer",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
|
|
@ -19,8 +19,12 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -285,8 +289,6 @@ func TestApply_defaultState(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serial := localState.State().Serial
|
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-auto-approve",
|
"-auto-approve",
|
||||||
testFixturePath("apply"),
|
testFixturePath("apply"),
|
||||||
|
@ -303,10 +305,6 @@ func TestApply_defaultState(t *testing.T) {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
t.Fatal("state should not be nil")
|
t.Fatal("state should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.Serial <= serial {
|
|
||||||
t.Fatalf("serial was not incremented. previous:%d, current%d", serial, state.Serial)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_error(t *testing.T) {
|
func TestApply_error(t *testing.T) {
|
||||||
|
@ -499,9 +497,7 @@ func TestApply_plan(t *testing.T) {
|
||||||
defaultInputReader = new(bytes.Buffer)
|
defaultInputReader = new(bytes.Buffer)
|
||||||
defaultInputWriter = new(bytes.Buffer)
|
defaultInputWriter = new(bytes.Buffer)
|
||||||
|
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
})
|
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -536,8 +532,7 @@ func TestApply_plan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_plan_backup(t *testing.T) {
|
func TestApply_plan_backup(t *testing.T) {
|
||||||
plan := testPlan(t)
|
planPath := testPlanFileNoop(t)
|
||||||
planPath := testPlanFile(t, plan)
|
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
backupPath := testTempFile(t)
|
backupPath := testTempFile(t)
|
||||||
|
|
||||||
|
@ -551,7 +546,7 @@ func TestApply_plan_backup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a state file that needs to be backed up
|
// create a state file that needs to be backed up
|
||||||
err := (&state.LocalState{Path: statePath}).WriteState(plan.State)
|
err := statemgr.NewFilesystem(statePath).WriteState(states.NewState())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -569,7 +564,7 @@ func TestApply_plan_backup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_plan_noBackup(t *testing.T) {
|
func TestApply_plan_noBackup(t *testing.T) {
|
||||||
planPath := testPlanFile(t, testPlan(t))
|
planPath := testPlanFileNoop(t)
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -620,14 +615,12 @@ func TestApply_plan_remoteState(t *testing.T) {
|
||||||
|
|
||||||
// Create a remote state
|
// Create a remote state
|
||||||
state := testState()
|
state := testState()
|
||||||
conf, srv := testRemoteState(t, state, 200)
|
backendState, srv := testRemoteState(t, state, 200)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
state.Remote = conf
|
testStateFileRemote(t, backendState)
|
||||||
|
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
_, snap := testModuleWithSnapshot(t, "apply")
|
||||||
Config: testModule(t, "apply"),
|
planPath := testPlanFile(t, snap, state, &plans.Plan{})
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -668,9 +661,7 @@ func TestApply_planWithVarFile(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
})
|
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
|
@ -710,9 +701,7 @@ func TestApply_planWithVarFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_planVars(t *testing.T) {
|
func TestApply_planVars(t *testing.T) {
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
})
|
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -743,9 +732,7 @@ func TestApply_planNoModuleFiles(t *testing.T) {
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
planFile := testPlanFile(t, &terraform.Plan{
|
planFile := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply-plan-no-module"),
|
|
||||||
})
|
|
||||||
|
|
||||||
apply := &ApplyCommand{
|
apply := &ApplyCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
|
@ -763,22 +750,20 @@ func TestApply_planNoModuleFiles(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_refresh(t *testing.T) {
|
func TestApply_refresh(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -909,22 +894,20 @@ func TestApply_shutdown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_state(t *testing.T) {
|
func TestApply_state(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -979,9 +962,6 @@ func TestApply_state(t *testing.T) {
|
||||||
|
|
||||||
backupState := testStateRead(t, statePath+DefaultBackupExtension)
|
backupState := testStateRead(t, statePath+DefaultBackupExtension)
|
||||||
|
|
||||||
// nil out the ConnInfo since that should not be restored
|
|
||||||
originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil
|
|
||||||
|
|
||||||
actualStr := strings.TrimSpace(backupState.String())
|
actualStr := strings.TrimSpace(backupState.String())
|
||||||
expectedStr := strings.TrimSpace(originalState.String())
|
expectedStr := strings.TrimSpace(originalState.String())
|
||||||
if actualStr != expectedStr {
|
if actualStr != expectedStr {
|
||||||
|
@ -1039,62 +1019,6 @@ func TestApply_sensitiveOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_stateFuture(t *testing.T) {
|
|
||||||
originalState := testState()
|
|
||||||
originalState.TFVersion = "99.99.99"
|
|
||||||
statePath := testStateFile(t, originalState)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &ApplyCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
"-auto-approve",
|
|
||||||
testFixturePath("apply"),
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code == 0 {
|
|
||||||
t.Fatal("should fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := testStateRead(t, statePath)
|
|
||||||
if !newState.Equal(originalState) {
|
|
||||||
t.Fatalf("bad: %#v", newState)
|
|
||||||
}
|
|
||||||
if newState.TFVersion != originalState.TFVersion {
|
|
||||||
t.Fatalf("bad: %#v", newState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApply_statePast(t *testing.T) {
|
|
||||||
originalState := testState()
|
|
||||||
originalState.TFVersion = "0.1.0"
|
|
||||||
statePath := testStateFile(t, originalState)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &ApplyCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
"-auto-approve",
|
|
||||||
testFixturePath("apply"),
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApply_vars(t *testing.T) {
|
func TestApply_vars(t *testing.T) {
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
|
@ -1285,23 +1209,20 @@ func TestApply_varFileDefaultJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_backup(t *testing.T) {
|
func TestApply_backup(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
originalState.Init()
|
})
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
backupPath := testTempFile(t)
|
backupPath := testTempFile(t)
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
names, _ := b.States()
|
names, _ := b.Workspaces()
|
||||||
return names
|
return names
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/terraform/helper/slowmessage"
|
"github.com/hashicorp/terraform/helper/slowmessage"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
)
|
)
|
||||||
|
@ -60,8 +61,8 @@ that no one else is holding a lock.
|
||||||
// Unlock, which is at a minimum the LockID string returned by the
|
// Unlock, which is at a minimum the LockID string returned by the
|
||||||
// state.Locker.
|
// state.Locker.
|
||||||
type Locker interface {
|
type Locker interface {
|
||||||
// Lock the provided state, storing the reason string in the LockInfo.
|
// Lock the provided state manager, storing the reason string in the LockInfo.
|
||||||
Lock(s state.State, reason string) error
|
Lock(s statemgr.Locker, reason string) error
|
||||||
// Unlock the previously locked state.
|
// Unlock the previously locked state.
|
||||||
// An optional error can be passed in, and will be combined with any error
|
// An optional error can be passed in, and will be combined with any error
|
||||||
// from the Unlock operation.
|
// from the Unlock operation.
|
||||||
|
@ -72,7 +73,7 @@ type locker struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
state state.State
|
state statemgr.Locker
|
||||||
ui cli.Ui
|
ui cli.Ui
|
||||||
color *colorstring.Colorize
|
color *colorstring.Colorize
|
||||||
lockID string
|
lockID string
|
||||||
|
@ -100,7 +101,7 @@ func NewLocker(
|
||||||
// Locker locks the given state and outputs to the user if locking is taking
|
// Locker locks the given state and outputs to the user if locking is taking
|
||||||
// longer than the threshold. The lock is retried until the context is
|
// longer than the threshold. The lock is retried until the context is
|
||||||
// cancelled.
|
// cancelled.
|
||||||
func (l *locker) Lock(s state.State, reason string) error {
|
func (l *locker) Lock(s statemgr.Locker, reason string) error {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
@ -113,7 +114,7 @@ func (l *locker) Lock(s state.State, reason string) error {
|
||||||
lockInfo.Operation = reason
|
lockInfo.Operation = reason
|
||||||
|
|
||||||
err := slowmessage.Do(LockThreshold, func() error {
|
err := slowmessage.Do(LockThreshold, func() error {
|
||||||
id, err := state.LockWithContext(ctx, s, lockInfo)
|
id, err := statemgr.LockWithContext(ctx, s, lockInfo)
|
||||||
l.lockID = id
|
l.lockID = id
|
||||||
return err
|
return err
|
||||||
}, func() {
|
}, func() {
|
||||||
|
@ -165,7 +166,7 @@ func NewNoopLocker() Locker {
|
||||||
return noopLocker{}
|
return noopLocker{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l noopLocker) Lock(state.State, string) error {
|
func (l noopLocker) Lock(statemgr.Locker, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,20 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/hashicorp/terraform/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is the directory where our test fixtures are.
|
// This is the directory where our test fixtures are.
|
||||||
|
@ -115,6 +125,12 @@ func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr ter
|
||||||
|
|
||||||
func testModule(t *testing.T, name string) *configs.Config {
|
func testModule(t *testing.T, name string) *configs.Config {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
c, _ := testModuleWithSnapshot(t, name)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *configload.Snapshot) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
dir := filepath.Join(fixtureDir, name)
|
dir := filepath.Join(fixtureDir, name)
|
||||||
|
|
||||||
|
@ -131,48 +147,59 @@ func testModule(t *testing.T, name string) *configs.Config {
|
||||||
t.Fatal(diags.Error())
|
t.Fatal(diags.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
config, diags := loader.LoadConfig(dir)
|
config, snap, diags := loader.LoadConfigWithSnapshot(dir)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
t.Fatal(diags.Error())
|
t.Fatal(diags.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config, snap
|
||||||
}
|
}
|
||||||
|
|
||||||
// testPlan returns a non-nil noop plan.
|
// testPlan returns a non-nil noop plan.
|
||||||
func testPlan(t *testing.T) *terraform.Plan {
|
func testPlan(t *testing.T) *plans.Plan {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
return &plans.Plan{
|
||||||
state := terraform.NewState()
|
Changes: plans.NewChanges(),
|
||||||
state.RootModule().Outputs["foo"] = &terraform.OutputState{
|
|
||||||
Type: "string",
|
|
||||||
Value: "foo",
|
|
||||||
}
|
|
||||||
|
|
||||||
return &terraform.Plan{
|
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
State: state,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
path := testTempFile(t)
|
stateFile := &statefile.File{
|
||||||
|
Lineage: "command.testPlanFile",
|
||||||
f, err := os.Create(path)
|
State: state,
|
||||||
if err != nil {
|
TerraformVersion: version.SemVer,
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err := terraform.WritePlan(plan, f); err != nil {
|
path := testTempFile(t)
|
||||||
t.Fatalf("err: %s", err)
|
err := planfile.Create(path, configSnap, stateFile, plan)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create temporary plan file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testPlanFileNoop is a shortcut function that creates a plan file that
|
||||||
|
// represents no changes and returns its path. This is useful when a test
|
||||||
|
// just needs any plan file, and it doesn't matter what is inside it.
|
||||||
|
func testPlanFileNoop(t *testing.T) string {
|
||||||
|
snap := &configload.Snapshot{
|
||||||
|
Modules: map[string]*configload.SnapshotModule{
|
||||||
|
"": {
|
||||||
|
Dir: ".",
|
||||||
|
Files: map[string][]byte{
|
||||||
|
"main.tf": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state := states.NewState()
|
||||||
|
plan := testPlan(t)
|
||||||
|
return testPlanFile(t, snap, state, plan)
|
||||||
|
}
|
||||||
|
|
||||||
func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -191,40 +218,110 @@ func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
// testState returns a test State structure that we use for a lot of tests.
|
// testState returns a test State structure that we use for a lot of tests.
|
||||||
func testState() *terraform.State {
|
func testState() *states.State {
|
||||||
state := &terraform.State{
|
return states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
Outputs: map[string]*terraform.OutputState{},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{
|
||||||
}
|
Type: "test",
|
||||||
state.Init()
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
return state
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStateFile(t *testing.T, s *terraform.State) string {
|
// writeStateForTesting is a helper that writes the given naked state to the
|
||||||
|
// given writer, generating a stub *statefile.File wrapper which is then
|
||||||
|
// immediately discarded.
|
||||||
|
func writeStateForTesting(state *states.State, w io.Writer) error {
|
||||||
|
sf := &statefile.File{
|
||||||
|
Serial: 0,
|
||||||
|
Lineage: "fake-for-testing",
|
||||||
|
State: state,
|
||||||
|
}
|
||||||
|
return statefile.Write(sf, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testStateMgrCurrentLineage returns the current lineage for the given state
|
||||||
|
// manager, or the empty string if it does not use lineage. This is primarily
|
||||||
|
// for testing against the local backend, which always supports lineage.
|
||||||
|
func testStateMgrCurrentLineage(mgr statemgr.Persistent) string {
|
||||||
|
if pm, ok := mgr.(statemgr.PersistentMeta); ok {
|
||||||
|
m := pm.StateSnapshotMeta()
|
||||||
|
return m.Lineage
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// markStateForMatching is a helper that writes a specific marker value to
|
||||||
|
// a state so that it can be recognized later with getStateMatchingMarker.
|
||||||
|
//
|
||||||
|
// Internally this just sets a root module output value called "testing_mark"
|
||||||
|
// to the given string value. If the state is being checked in other ways,
|
||||||
|
// the test code may need to compensate for the addition or overwriting of this
|
||||||
|
// special output value name.
|
||||||
|
//
|
||||||
|
// The given mark string is returned verbatim, to allow the following pattern
|
||||||
|
// in tests:
|
||||||
|
//
|
||||||
|
// mark := markStateForMatching(state, "foo")
|
||||||
|
// // (do stuff to the state)
|
||||||
|
// assertStateHasMarker(state, mark)
|
||||||
|
func markStateForMatching(state *states.State, mark string) string {
|
||||||
|
state.RootModule().SetOutputValue("testing_mark", cty.StringVal(mark), false)
|
||||||
|
return mark
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStateMatchingMarker is used with markStateForMatching to retrieve the
|
||||||
|
// mark string previously added to the given state. If no such mark is present,
|
||||||
|
// the result is an empty string.
|
||||||
|
func getStateMatchingMarker(state *states.State) string {
|
||||||
|
os := state.RootModule().OutputValues["testing_mark"]
|
||||||
|
if os == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v := os.Value
|
||||||
|
if v.Type() == cty.String && v.IsKnown() && !v.IsNull() {
|
||||||
|
return v.AsString()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateHasMarker is a helper around getStateMatchingMarker that also includes
|
||||||
|
// the equality test, for more convenient use in test assertion branches.
|
||||||
|
func stateHasMarker(state *states.State, want string) bool {
|
||||||
|
return getStateMatchingMarker(state) == want
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertStateHasMarker wraps stateHasMarker to automatically generate a
|
||||||
|
// fatal test result (i.e. t.Fatal) if the marker doesn't match.
|
||||||
|
func assertStateHasMarker(t *testing.T, state *states.State, want string) {
|
||||||
|
if !stateHasMarker(state, want) {
|
||||||
|
t.Fatalf("wrong state marker\ngot: %q\nwant: %q", getStateMatchingMarker(state), want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStateFile(t *testing.T, s *states.State) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
path := testTempFile(t)
|
path := testTempFile(t)
|
||||||
|
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("failed to create temporary state file %s: %s", path, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
if err := terraform.WriteState(s, f); err != nil {
|
err = writeStateForTesting(s, f)
|
||||||
t.Fatalf("err: %s", err)
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write state to temporary file %s: %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
@ -272,7 +369,7 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// testStateRead reads the state from a file
|
// testStateRead reads the state from a file
|
||||||
func testStateRead(t *testing.T, path string) *terraform.State {
|
func testStateRead(t *testing.T, path string) *states.State {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
@ -281,12 +378,34 @@ func testStateRead(t *testing.T, path string) *terraform.State {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
sf, err := statefile.Read(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newState
|
return sf.State
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDataStateRead reads a "data state", which is a file format resembling
|
||||||
|
// our state format v3 that is used only to track current backend settings.
|
||||||
|
//
|
||||||
|
// This old format still uses *terraform.State, but should be replaced with
|
||||||
|
// a more specialized type in a later release.
|
||||||
|
func testDataStateRead(t *testing.T, path string) *terraform.State {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
s, err := terraform.ReadState(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// testStateOutput tests that the state at the given path contains
|
// testStateOutput tests that the state at the given path contains
|
||||||
|
@ -571,7 +690,11 @@ func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State
|
||||||
|
|
||||||
// testRemoteState is used to make a test HTTP server to return a given
|
// testRemoteState is used to make a test HTTP server to return a given
|
||||||
// state file that can be used for testing legacy remote state.
|
// state file that can be used for testing legacy remote state.
|
||||||
func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) {
|
//
|
||||||
|
// The return values are a *terraform.State instance that should be written
|
||||||
|
// as the "data state" (really: backend state) and the server that the
|
||||||
|
// returned data state refers to.
|
||||||
|
func testRemoteState(t *testing.T, s *states.State, c int) (*terraform.State, *httptest.Server) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var b64md5 string
|
var b64md5 string
|
||||||
|
@ -591,25 +714,32 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote
|
||||||
resp.Write(buf.Bytes())
|
resp.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retState := terraform.NewState()
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(cb))
|
srv := httptest.NewServer(http.HandlerFunc(cb))
|
||||||
remote := &terraform.RemoteState{
|
b := &terraform.BackendState{
|
||||||
Type: "http",
|
Type: "http",
|
||||||
Config: map[string]string{"address": srv.URL},
|
|
||||||
}
|
}
|
||||||
|
b.SetConfig(cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"address": cty.StringVal(srv.URL),
|
||||||
|
}), &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"address": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
retState.Backend = b
|
||||||
|
|
||||||
if s != nil {
|
if s != nil {
|
||||||
// Set the remote data
|
err := statefile.Write(&statefile.File{State: s}, buf)
|
||||||
s.Remote = remote
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write initial state: %v", err)
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(s); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
}
|
||||||
md5 := md5.Sum(buf.Bytes())
|
|
||||||
b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return remote, srv
|
return retState, srv
|
||||||
}
|
}
|
||||||
|
|
||||||
// testlockState calls a separate process to the lock the state file at path.
|
// testlockState calls a separate process to the lock the state file at path.
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (c *ConsoleCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(b)
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.ConfigLoader, err = c.initConfigLoader()
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,9 +6,12 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Plan is a representation of a plan optimized for display to
|
// Plan is a representation of a plan optimized for display to
|
||||||
|
@ -59,100 +62,63 @@ type PlanStats struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlan produces a display-oriented Plan from a terraform.Plan.
|
// NewPlan produces a display-oriented Plan from a terraform.Plan.
|
||||||
func NewPlan(plan *terraform.Plan) *Plan {
|
func NewPlan(changes *plans.Changes) *Plan {
|
||||||
ret := &Plan{}
|
ret := &Plan{}
|
||||||
if plan == nil || plan.Diff == nil || plan.Diff.Empty() {
|
if changes == nil {
|
||||||
// Nothing to do!
|
// Nothing to do!
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range plan.Diff.Modules {
|
for _, rc := range changes.Resources {
|
||||||
var modulePath []string
|
addr := rc.Addr
|
||||||
if !m.IsRoot() {
|
dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
|
||||||
// trim off the leading "root" path segment, since it's implied
|
|
||||||
// when we use a path in a resource address.
|
// We create "delete" actions for data resources so we can clean
|
||||||
modulePath = m.Path[1:]
|
// up their entries in state, but this is an implementation detail
|
||||||
|
// that users shouldn't see.
|
||||||
|
if dataSource && rc.Action == plans.Delete {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, r := range m.Resources {
|
// For now we'll shim this to work with our old types.
|
||||||
if r.Empty() {
|
// TODO: Update for the new plan types, ideally also switching over to
|
||||||
continue
|
// a structural diff renderer instead of a flat renderer.
|
||||||
}
|
did := &InstanceDiff{
|
||||||
|
Addr: terraform.NewLegacyResourceInstanceAddress(addr),
|
||||||
|
}
|
||||||
|
|
||||||
addr, err := terraform.ParseResourceAddressForInstanceDiff(modulePath, k)
|
switch rc.Action {
|
||||||
if err != nil {
|
case plans.Create:
|
||||||
// should never happen; indicates invalid diff
|
if dataSource {
|
||||||
panic("invalid resource address in diff")
|
// Use "refresh" as the action for display, but core
|
||||||
}
|
// currently uses Create for this internally.
|
||||||
|
// FIXME: Update core to generate plans.Read for this case
|
||||||
dataSource := addr.Mode == config.DataResourceMode
|
// instead.
|
||||||
|
|
||||||
// We create "destroy" actions for data resources so we can clean
|
|
||||||
// up their entries in state, but this is an implementation detail
|
|
||||||
// that users shouldn't see.
|
|
||||||
if dataSource && r.ChangeType() == terraform.DiffDestroy {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
did := &InstanceDiff{
|
|
||||||
Addr: addr,
|
|
||||||
Action: r.ChangeType(),
|
|
||||||
Tainted: r.DestroyTainted,
|
|
||||||
Deposed: r.DestroyDeposed,
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataSource && did.Action == terraform.DiffCreate {
|
|
||||||
// Use "refresh" as the action for display, since core
|
|
||||||
// currently uses Create for this.
|
|
||||||
did.Action = terraform.DiffRefresh
|
did.Action = terraform.DiffRefresh
|
||||||
|
} else {
|
||||||
|
did.Action = terraform.DiffCreate
|
||||||
}
|
}
|
||||||
|
case plans.Read:
|
||||||
ret.Resources = append(ret.Resources, did)
|
did.Action = terraform.DiffRefresh
|
||||||
|
case plans.Delete:
|
||||||
if did.Action == terraform.DiffDestroy {
|
did.Action = terraform.DiffDestroy
|
||||||
// Don't show any outputs for destroy actions
|
case plans.Replace:
|
||||||
continue
|
did.Action = terraform.DiffDestroyCreate
|
||||||
}
|
case plans.Update:
|
||||||
|
did.Action = terraform.DiffUpdate
|
||||||
for k, a := range r.Attributes {
|
default:
|
||||||
var action terraform.DiffChangeType
|
panic(fmt.Sprintf("unexpected change action %s", rc.Action))
|
||||||
switch {
|
|
||||||
case a.NewRemoved:
|
|
||||||
action = terraform.DiffDestroy
|
|
||||||
case did.Action == terraform.DiffCreate:
|
|
||||||
action = terraform.DiffCreate
|
|
||||||
default:
|
|
||||||
action = terraform.DiffUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
did.Attributes = append(did.Attributes, &AttributeDiff{
|
|
||||||
Path: k,
|
|
||||||
Action: action,
|
|
||||||
|
|
||||||
OldValue: a.Old,
|
|
||||||
NewValue: a.New,
|
|
||||||
|
|
||||||
Sensitive: a.Sensitive,
|
|
||||||
ForcesNew: a.RequiresNew,
|
|
||||||
NewComputed: a.NewComputed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the attributes by their paths for display
|
|
||||||
sort.Slice(did.Attributes, func(i, j int) bool {
|
|
||||||
iPath := did.Attributes[i].Path
|
|
||||||
jPath := did.Attributes[j].Path
|
|
||||||
|
|
||||||
// as a special case, "id" is always first
|
|
||||||
switch {
|
|
||||||
case iPath != jPath && (iPath == "id" || jPath == "id"):
|
|
||||||
return iPath == "id"
|
|
||||||
default:
|
|
||||||
return iPath < jPath
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rc.DeposedKey != states.NotDeposed {
|
||||||
|
did.Deposed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this is just a temporary stub implementation on the way
|
||||||
|
// to us replacing this with the structural diff renderer, we currently
|
||||||
|
// don't include any attributes here.
|
||||||
|
// FIXME: Implement the structural diff renderer to replace this
|
||||||
|
// codepath altogether.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the instance diffs by their addresses for display.
|
// Sort the instance diffs by their addresses for display.
|
||||||
|
|
|
@ -6,14 +6,16 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateOpts are the options for formatting a state.
|
// StateOpts are the options for formatting a state.
|
||||||
type StateOpts struct {
|
type StateOpts struct {
|
||||||
// State is the state to format. This is required.
|
// State is the state to format. This is required.
|
||||||
State *terraform.State
|
State *states.State
|
||||||
|
|
||||||
// Color is the colorizer. This is optional.
|
// Color is the colorizer. This is optional.
|
||||||
Color *colorstring.Colorize
|
Color *colorstring.Colorize
|
||||||
|
@ -34,7 +36,10 @@ func State(opts *StateOpts) string {
|
||||||
return "The state file is empty. No resources are represented."
|
return "The state file is empty. No resources are represented."
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
// FIXME: State formatter not yet updated for new state types
|
||||||
|
return "FIXME: State formatter not yet updated for new state types"
|
||||||
|
|
||||||
|
/*var buf bytes.Buffer
|
||||||
buf.WriteString("[reset]")
|
buf.WriteString("[reset]")
|
||||||
|
|
||||||
// Format all the modules
|
// Format all the modules
|
||||||
|
@ -76,6 +81,7 @@ func State(opts *StateOpts) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts.Color.Color(strings.TrimSpace(buf.String()))
|
return opts.Color.Color(strings.TrimSpace(buf.String()))
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatStateModuleExpand(
|
func formatStateModuleExpand(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
@ -46,12 +47,13 @@ func (c *GraphCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is a plan
|
// Check if the path is a plan
|
||||||
plan, err := c.Plan(configPath)
|
var plan *plans.Plan
|
||||||
|
planFile, err := c.PlanFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if plan != nil {
|
if planFile != nil {
|
||||||
// Reset for backend loading
|
// Reset for backend loading
|
||||||
configPath = ""
|
configPath = ""
|
||||||
}
|
}
|
||||||
|
@ -84,10 +86,10 @@ func (c *GraphCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(b)
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.ConfigLoader, err = c.initConfigLoader()
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
opReq.Plan = plan
|
opReq.PlanFile = planFile
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
|
|
|
@ -5,8 +5,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGraph(t *testing.T) {
|
func TestGraph(t *testing.T) {
|
||||||
|
@ -107,22 +110,25 @@ func TestGraph_plan(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
plan := &plans.Plan{
|
||||||
Diff: &terraform.Diff{
|
Changes: plans.NewChanges(),
|
||||||
Modules: []*terraform.ModuleDiff{
|
}
|
||||||
&terraform.ModuleDiff{
|
plan.Changes.Resources = append(plan.Changes.Resources, &plans.ResourceInstanceChangeSrc{
|
||||||
Path: []string{"root"},
|
Addr: addrs.Resource{
|
||||||
Resources: map[string]*terraform.InstanceDiff{
|
Mode: addrs.ManagedResourceMode,
|
||||||
"test_instance.bar": &terraform.InstanceDiff{
|
Type: "test_instance",
|
||||||
Destroy: true,
|
Name: "bar",
|
||||||
},
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
},
|
ChangeSrc: plans.ChangeSrc{
|
||||||
},
|
Action: plans.Delete,
|
||||||
},
|
Before: plans.DynamicValue(`{}`),
|
||||||
|
After: plans.DynamicValue(`null`),
|
||||||
},
|
},
|
||||||
|
ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
Config: testModule(t, "graph"),
|
|
||||||
})
|
})
|
||||||
|
_, configSnap := testModuleWithSnapshot(t, "graph")
|
||||||
|
|
||||||
|
planPath := testPlanFile(t, configSnap, states.NewState(), plan)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &GraphCommand{
|
c := &GraphCommand{
|
||||||
|
|
|
@ -10,9 +10,15 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPeriodicUiTimer = 10 * time.Second
|
const defaultPeriodicUiTimer = 10 * time.Second
|
||||||
|
@ -31,6 +37,8 @@ type UiHook struct {
|
||||||
ui cli.Ui
|
ui cli.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ terraform.Hook = (*UiHook)(nil)
|
||||||
|
|
||||||
// uiResourceState tracks the state of a single resource
|
// uiResourceState tracks the state of a single resource
|
||||||
type uiResourceState struct {
|
type uiResourceState struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -53,37 +61,21 @@ const (
|
||||||
uiResourceDestroy
|
uiResourceDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *UiHook) PreApply(
|
func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
d *terraform.InstanceDiff) (terraform.HookAction, error) {
|
|
||||||
h.once.Do(h.init)
|
h.once.Do(h.init)
|
||||||
|
|
||||||
// if there's no diff, there's nothing to output
|
|
||||||
if d.Empty() {
|
|
||||||
return terraform.HookActionContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
id := n.HumanId()
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
|
|
||||||
op := uiResourceModify
|
|
||||||
if d.Destroy {
|
|
||||||
op = uiResourceDestroy
|
|
||||||
} else if s.ID == "" {
|
|
||||||
op = uiResourceCreate
|
|
||||||
}
|
|
||||||
|
|
||||||
var operation string
|
var operation string
|
||||||
switch op {
|
var op uiResourceOp
|
||||||
case uiResourceModify:
|
switch action {
|
||||||
operation = "Modifying..."
|
case plans.Delete:
|
||||||
case uiResourceDestroy:
|
|
||||||
operation = "Destroying..."
|
operation = "Destroying..."
|
||||||
case uiResourceCreate:
|
op = uiResourceDestroy
|
||||||
|
case plans.Create:
|
||||||
operation = "Creating..."
|
operation = "Creating..."
|
||||||
case uiResourceUnknown:
|
op = uiResourceCreate
|
||||||
return terraform.HookActionContinue, nil
|
default:
|
||||||
|
operation = "Modifying..."
|
||||||
|
op = uiResourceModify
|
||||||
}
|
}
|
||||||
|
|
||||||
attrBuf := new(bytes.Buffer)
|
attrBuf := new(bytes.Buffer)
|
||||||
|
@ -92,7 +84,11 @@ func (h *UiHook) PreApply(
|
||||||
// determine the longest key so that we can align them all.
|
// determine the longest key so that we can align them all.
|
||||||
keyLen := 0
|
keyLen := 0
|
||||||
|
|
||||||
dAttrs := d.CopyAttributes()
|
// FIXME: This is stubbed out in preparation for rewriting it to use
|
||||||
|
// a structural presentation rather than the old-style flatmap one.
|
||||||
|
// We just assume no attributes at all for now, pending new code to
|
||||||
|
// work with the two cty.Values we are given.
|
||||||
|
dAttrs := map[string]terraform.ResourceAttrDiff{}
|
||||||
keys := make([]string, 0, len(dAttrs))
|
keys := make([]string, 0, len(dAttrs))
|
||||||
for key, _ := range dAttrs {
|
for key, _ := range dAttrs {
|
||||||
// Skip the ID since we do that specially
|
// Skip the ID since we do that specially
|
||||||
|
@ -109,7 +105,7 @@ func (h *UiHook) PreApply(
|
||||||
|
|
||||||
// Go through and output each attribute
|
// Go through and output each attribute
|
||||||
for _, attrK := range keys {
|
for _, attrK := range keys {
|
||||||
attrDiff, _ := d.GetAttribute(attrK)
|
attrDiff := dAttrs[attrK]
|
||||||
|
|
||||||
v := attrDiff.New
|
v := attrDiff.New
|
||||||
u := attrDiff.Old
|
u := attrDiff.Old
|
||||||
|
@ -136,18 +132,16 @@ func (h *UiHook) PreApply(
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateId, stateIdSuffix string
|
var stateId, stateIdSuffix string
|
||||||
if s != nil && s.ID != "" {
|
|
||||||
stateId = s.ID
|
|
||||||
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][bold]%s: %s%s[reset]%s",
|
"[reset][bold]%s: %s%s[reset]%s",
|
||||||
addr,
|
addr,
|
||||||
operation,
|
operation,
|
||||||
stateIdSuffix,
|
stateIdSuffix,
|
||||||
attrString)))
|
attrString,
|
||||||
|
)))
|
||||||
|
|
||||||
|
id := addr.String()
|
||||||
uiState := uiResourceState{
|
uiState := uiResourceState{
|
||||||
Name: id,
|
Name: id,
|
||||||
ResourceId: stateId,
|
ResourceId: stateId,
|
||||||
|
@ -205,13 +199,9 @@ func (h *UiHook) stillApplying(state uiResourceState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PostApply(
|
func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, applyerr error) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
applyerr error) (terraform.HookAction, error) {
|
|
||||||
|
|
||||||
id := n.HumanId()
|
id := addr.String()
|
||||||
addr := n.ResourceAddress()
|
|
||||||
|
|
||||||
h.l.Lock()
|
h.l.Lock()
|
||||||
state := h.resources[id]
|
state := h.resources[id]
|
||||||
|
@ -223,9 +213,6 @@ func (h *UiHook) PostApply(
|
||||||
h.l.Unlock()
|
h.l.Unlock()
|
||||||
|
|
||||||
var stateIdSuffix string
|
var stateIdSuffix string
|
||||||
if s != nil && s.ID != "" {
|
|
||||||
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg string
|
var msg string
|
||||||
switch state.Op {
|
switch state.Op {
|
||||||
|
@ -253,31 +240,23 @@ func (h *UiHook) PostApply(
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PreDiff(
|
func (h *UiHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState) (terraform.HookAction, error) {
|
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PreProvision(
|
func (h *UiHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
provId string) (terraform.HookAction, error) {
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][bold]%s: Provisioning with '%s'...[reset]",
|
"[reset][bold]%s: Provisioning with '%s'...[reset]",
|
||||||
addr, provId)))
|
addr, typeName,
|
||||||
|
)))
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) ProvisionOutput(
|
func (h *UiHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, msg string) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
provId string,
|
|
||||||
msg string) {
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString(h.Colorize.Color("[reset]"))
|
buf.WriteString(h.Colorize.Color("[reset]"))
|
||||||
|
|
||||||
prefix := fmt.Sprintf("%s (%s): ", addr, provId)
|
prefix := fmt.Sprintf("%s (%s): ", addr, typeName)
|
||||||
s := bufio.NewScanner(strings.NewReader(msg))
|
s := bufio.NewScanner(strings.NewReader(msg))
|
||||||
s.Split(scanLines)
|
s.Split(scanLines)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
|
@ -290,19 +269,10 @@ func (h *UiHook) ProvisionOutput(
|
||||||
h.ui.Output(strings.TrimSpace(buf.String()))
|
h.ui.Output(strings.TrimSpace(buf.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PreRefresh(
|
func (h *UiHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState) (terraform.HookAction, error) {
|
|
||||||
h.once.Do(h.init)
|
h.once.Do(h.init)
|
||||||
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
|
|
||||||
var stateIdSuffix string
|
var stateIdSuffix string
|
||||||
// Data resources refresh before they have ids, whereas managed
|
|
||||||
// resources are only refreshed when they have ids.
|
|
||||||
if s.ID != "" {
|
|
||||||
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][bold]%s: Refreshing state...%s",
|
"[reset][bold]%s: Refreshing state...%s",
|
||||||
|
@ -310,30 +280,26 @@ func (h *UiHook) PreRefresh(
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PreImportState(
|
func (h *UiHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
id string) (terraform.HookAction, error) {
|
|
||||||
h.once.Do(h.init)
|
h.once.Do(h.init)
|
||||||
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][bold]%s: Importing from ID %q...",
|
"[reset][bold]%s: Importing from ID %q...",
|
||||||
addr, id)))
|
addr, importID,
|
||||||
|
)))
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UiHook) PostImportState(
|
func (h *UiHook) PostImportState(addr addrs.AbsResourceInstance, imported []*states.ImportedObject) (terraform.HookAction, error) {
|
||||||
n *terraform.InstanceInfo,
|
|
||||||
s []*terraform.InstanceState) (terraform.HookAction, error) {
|
|
||||||
h.once.Do(h.init)
|
h.once.Do(h.init)
|
||||||
|
|
||||||
addr := n.ResourceAddress()
|
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][bold][green]%s: Import complete!", addr)))
|
"[reset][bold][green]%s: Import complete!", addr)))
|
||||||
for _, s := range s {
|
for _, s := range imported {
|
||||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||||
"[reset][green] Imported %s (ID: %s)",
|
"[reset][green] Imported %s",
|
||||||
s.Ephemeral.Type, s.ID)))
|
s.ResourceType,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return terraform.HookActionContinue, nil
|
return terraform.HookActionContinue, nil
|
||||||
|
|
|
@ -6,9 +6,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUiHookPreApply_periodicTimer(t *testing.T) {
|
func TestUiHookPreApply_periodicTimer(t *testing.T) {
|
||||||
|
@ -30,28 +35,27 @@ func TestUiHookPreApply_periodicTimer(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{
|
addr := addrs.Resource{
|
||||||
Id: "data.aws_availability_zones.available",
|
Mode: addrs.DataResourceMode,
|
||||||
ModulePath: []string{"root"},
|
Type: "aws_availability_zones",
|
||||||
Type: "aws_availability_zones",
|
Name: "available",
|
||||||
}
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
s := &terraform.InstanceState{
|
priorState := cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
ID: "2017-03-05 10:56:59.298784526 +0000 UTC",
|
"id": cty.String,
|
||||||
Attributes: map[string]string{
|
"names": cty.List(cty.String),
|
||||||
"id": "2017-03-05 10:56:59.298784526 +0000 UTC",
|
}))
|
||||||
"names.#": "4",
|
plannedNewState := cty.ObjectVal(map[string]cty.Value{
|
||||||
"names.0": "us-east-1a",
|
"id": cty.StringVal("2017-03-05 10:56:59.298784526 +0000 UTC"),
|
||||||
"names.1": "us-east-1b",
|
"names": cty.ListVal([]cty.Value{
|
||||||
"names.2": "us-east-1c",
|
cty.StringVal("us-east-1a"),
|
||||||
"names.3": "us-east-1d",
|
cty.StringVal("us-east-1b"),
|
||||||
},
|
cty.StringVal("us-east-1c"),
|
||||||
}
|
cty.StringVal("us-east-1d"),
|
||||||
d := &terraform.InstanceDiff{
|
}),
|
||||||
Destroy: true,
|
})
|
||||||
}
|
|
||||||
|
|
||||||
action, err := h.PreApply(n, s, d)
|
action, err := h.PreApply(addr, states.CurrentGen, plans.Delete, priorState, plannedNewState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +66,7 @@ func TestUiHookPreApply_periodicTimer(t *testing.T) {
|
||||||
time.Sleep(3100 * time.Millisecond)
|
time.Sleep(3100 * time.Millisecond)
|
||||||
|
|
||||||
// stop the background writer
|
// stop the background writer
|
||||||
uiState := h.resources[n.HumanId()]
|
uiState := h.resources[addr.String()]
|
||||||
close(uiState.DoneCh)
|
close(uiState.DoneCh)
|
||||||
<-uiState.done
|
<-uiState.done
|
||||||
|
|
||||||
|
@ -101,28 +105,27 @@ func TestUiHookPreApply_destroy(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{
|
addr := addrs.Resource{
|
||||||
Id: "data.aws_availability_zones.available",
|
Mode: addrs.DataResourceMode,
|
||||||
ModulePath: []string{"root"},
|
Type: "aws_availability_zones",
|
||||||
Type: "aws_availability_zones",
|
Name: "available",
|
||||||
}
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
s := &terraform.InstanceState{
|
priorState := cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
ID: "2017-03-05 10:56:59.298784526 +0000 UTC",
|
"id": cty.String,
|
||||||
Attributes: map[string]string{
|
"names": cty.List(cty.String),
|
||||||
"id": "2017-03-05 10:56:59.298784526 +0000 UTC",
|
}))
|
||||||
"names.#": "4",
|
plannedNewState := cty.ObjectVal(map[string]cty.Value{
|
||||||
"names.0": "us-east-1a",
|
"id": cty.StringVal("2017-03-05 10:56:59.298784526 +0000 UTC"),
|
||||||
"names.1": "us-east-1b",
|
"names": cty.ListVal([]cty.Value{
|
||||||
"names.2": "us-east-1c",
|
cty.StringVal("us-east-1a"),
|
||||||
"names.3": "us-east-1d",
|
cty.StringVal("us-east-1b"),
|
||||||
},
|
cty.StringVal("us-east-1c"),
|
||||||
}
|
cty.StringVal("us-east-1d"),
|
||||||
d := &terraform.InstanceDiff{
|
}),
|
||||||
Destroy: true,
|
})
|
||||||
}
|
|
||||||
|
|
||||||
action, err := h.PreApply(n, s, d)
|
action, err := h.PreApply(addr, states.CurrentGen, plans.Delete, priorState, plannedNewState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -130,6 +133,11 @@ func TestUiHookPreApply_destroy(t *testing.T) {
|
||||||
t.Fatalf("Expected hook to continue, given: %#v", action)
|
t.Fatalf("Expected hook to continue, given: %#v", action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stop the background writer
|
||||||
|
uiState := h.resources[addr.String()]
|
||||||
|
close(uiState.DoneCh)
|
||||||
|
<-uiState.done
|
||||||
|
|
||||||
expectedOutput := "data.aws_availability_zones.available: Destroying... (ID: 2017-03-05 10:56:59.298784526 +0000 UTC)\n"
|
expectedOutput := "data.aws_availability_zones.available: Destroying... (ID: 2017-03-05 10:56:59.298784526 +0000 UTC)\n"
|
||||||
output := ui.OutputWriter.String()
|
output := ui.OutputWriter.String()
|
||||||
if output != expectedOutput {
|
if output != expectedOutput {
|
||||||
|
@ -161,12 +169,18 @@ func TestUiHookPostApply_emptyState(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &terraform.InstanceInfo{
|
addr := addrs.Resource{
|
||||||
Id: "data.google_compute_zones.available",
|
Mode: addrs.DataResourceMode,
|
||||||
ModulePath: []string{"root"},
|
Type: "google_compute_zones",
|
||||||
Type: "google_compute_zones",
|
Name: "available",
|
||||||
}
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||||
action, err := h.PostApply(n, nil, nil)
|
|
||||||
|
newState := cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"id": cty.String,
|
||||||
|
"names": cty.List(cty.String),
|
||||||
|
}))
|
||||||
|
|
||||||
|
action, err := h.PostApply(addr, states.CurrentGen, newState, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -208,7 +206,7 @@ func (c *ImportCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(b)
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.ConfigLoader, err = c.initConfigLoader()
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,17 +8,19 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/posener/complete"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/plugin"
|
"github.com/hashicorp/terraform/plugin"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/posener/complete"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitCommand is a Command implementation that takes a Terraform
|
// InitCommand is a Command implementation that takes a Terraform
|
||||||
|
@ -266,13 +268,13 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var state *terraform.State
|
var state *states.State
|
||||||
|
|
||||||
// If we have a functional backend (either just initialized or initialized
|
// If we have a functional backend (either just initialized or initialized
|
||||||
// on a previous run) we'll use the current state as a potential source
|
// on a previous run) we'll use the current state as a potential source
|
||||||
// of provider dependencies.
|
// of provider dependencies.
|
||||||
if back != nil {
|
if back != nil {
|
||||||
sMgr, err := back.State(c.Workspace())
|
sMgr, err := back.StateMgr(c.Workspace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -391,17 +393,12 @@ func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configsc
|
||||||
|
|
||||||
// Load the complete module tree, and fetch any missing providers.
|
// Load the complete module tree, and fetch any missing providers.
|
||||||
// This method outputs its own Ui.
|
// This method outputs its own Ui.
|
||||||
func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) tfdiags.Diagnostics {
|
func (c *InitCommand) getProviders(path string, state *states.State, upgrade bool) tfdiags.Diagnostics {
|
||||||
config, diags := c.loadConfig(path)
|
config, diags := c.loadConfig(path)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := terraform.CheckStateVersion(state, false); err != nil {
|
|
||||||
diags = diags.Append(err)
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
var available discovery.PluginMetaSet
|
var available discovery.PluginMetaSet
|
||||||
if upgrade {
|
if upgrade {
|
||||||
// If we're in upgrade mode, we ignore any auto-installed plugins
|
// If we're in upgrade mode, we ignore any auto-installed plugins
|
||||||
|
|
|
@ -285,8 +285,7 @@ func TestInit_backendUnset(t *testing.T) {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
s := testStateRead(t, filepath.Join(
|
s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
DefaultDataDir, DefaultStateFilename))
|
|
||||||
if !s.Backend.Empty() {
|
if !s.Backend.Empty() {
|
||||||
t.Fatal("should not have backend config")
|
t.Fatal("should not have backend config")
|
||||||
}
|
}
|
||||||
|
@ -314,7 +313,7 @@ func TestInit_backendConfigFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read our saved backend config and verify we have our settings
|
// Read our saved backend config and verify we have our settings
|
||||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -346,7 +345,7 @@ func TestInit_backendConfigFileChange(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read our saved backend config and verify we have our settings
|
// Read our saved backend config and verify we have our settings
|
||||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -373,7 +372,7 @@ func TestInit_backendConfigKV(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read our saved backend config and verify we have our settings
|
// Read our saved backend config and verify we have our settings
|
||||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -447,7 +446,7 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read our saved backend config and verify we have our settings
|
// Read our saved backend config and verify we have our settings
|
||||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -460,7 +459,7 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"hello"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -489,7 +488,7 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read our saved backend config and verify we have our settings
|
// Read our saved backend config and verify we have our settings
|
||||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
if got, want := string(state.Backend.ConfigRaw), `{"path":"foo"}`; got != want {
|
if got, want := string(state.Backend.ConfigRaw), `{"path":"foo"}`; got != want {
|
||||||
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong config\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
@ -506,7 +505,7 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
|
|
||||||
if state.Backend.Hash == backendHash {
|
if state.Backend.Hash == backendHash {
|
||||||
t.Fatal("state.Backend.Hash was not updated")
|
t.Fatal("state.Backend.Hash was not updated")
|
||||||
|
|
|
@ -318,13 +318,12 @@ const (
|
||||||
// context with the settings from this Meta.
|
// context with the settings from this Meta.
|
||||||
func (m *Meta) contextOpts() *terraform.ContextOpts {
|
func (m *Meta) contextOpts() *terraform.ContextOpts {
|
||||||
var opts terraform.ContextOpts
|
var opts terraform.ContextOpts
|
||||||
opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}}
|
opts.Hooks = []terraform.Hook{m.uiHook()}
|
||||||
opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
|
opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
|
||||||
|
|
||||||
opts.Targets = m.targets
|
opts.Targets = m.targets
|
||||||
opts.UIInput = m.UIInput()
|
opts.UIInput = m.UIInput()
|
||||||
opts.Parallelism = m.parallelism
|
opts.Parallelism = m.parallelism
|
||||||
opts.Shadow = m.shadow
|
|
||||||
|
|
||||||
// If testingOverrides are set, we'll skip the plugin discovery process
|
// If testingOverrides are set, we'll skip the plugin discovery process
|
||||||
// and just work with what we've been given, thus allowing the tests
|
// and just work with what we've been given, thus allowing the tests
|
||||||
|
|
|
@ -15,16 +15,18 @@ import (
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl2/hcldec"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
backendlocal "github.com/hashicorp/terraform/backend/local"
|
backendlocal "github.com/hashicorp/terraform/backend/local"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackendOpts are the options used to initialize a backend.Backend.
|
// BackendOpts are the options used to initialize a backend.Backend.
|
||||||
|
@ -38,10 +40,6 @@ type BackendOpts struct {
|
||||||
// arguments in Config.
|
// arguments in Config.
|
||||||
ConfigOverride hcl.Body
|
ConfigOverride hcl.Body
|
||||||
|
|
||||||
// Plan is a plan that is being used. If this is set, the backend
|
|
||||||
// configuration and output configuration will come from this plan.
|
|
||||||
Plan *terraform.Plan
|
|
||||||
|
|
||||||
// Init should be set to true if initialization is allowed. If this is
|
// Init should be set to true if initialization is allowed. If this is
|
||||||
// false, then any configuration that requires configuration will show
|
// false, then any configuration that requires configuration will show
|
||||||
// an error asking the user to reinitialize.
|
// an error asking the user to reinitialize.
|
||||||
|
@ -78,17 +76,9 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
|
||||||
// local operation.
|
// local operation.
|
||||||
var b backend.Backend
|
var b backend.Backend
|
||||||
if !opts.ForceLocal {
|
if !opts.ForceLocal {
|
||||||
// If we have a plan then, we get the the backend from there. Otherwise,
|
var backendDiags tfdiags.Diagnostics
|
||||||
// the backend comes from the configuration.
|
b, backendDiags = m.backendFromConfig(opts)
|
||||||
if opts.Plan != nil {
|
diags = diags.Append(backendDiags)
|
||||||
var backendDiags tfdiags.Diagnostics
|
|
||||||
b, backendDiags = m.backendFromPlan(opts)
|
|
||||||
diags = diags.Append(backendDiags)
|
|
||||||
} else {
|
|
||||||
var backendDiags tfdiags.Diagnostics
|
|
||||||
b, backendDiags = m.backendFromConfig(opts)
|
|
||||||
diags = diags.Append(backendDiags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
|
@ -97,23 +87,9 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
|
||||||
log.Printf("[INFO] command: backend initialized: %T", b)
|
log.Printf("[INFO] command: backend initialized: %T", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the CLI opts we pass into backends that support it.
|
// Setup the CLI opts we pass into backends that support it
|
||||||
cliOpts := &backend.CLIOpts{
|
cliOpts := m.backendCLIOpts()
|
||||||
CLI: m.Ui,
|
cliOpts.Validation = true
|
||||||
CLIColor: m.Colorize(),
|
|
||||||
ShowDiagnostics: m.showDiagnostics,
|
|
||||||
StatePath: m.statePath,
|
|
||||||
StateOutPath: m.stateOutPath,
|
|
||||||
StateBackupPath: m.backupPath,
|
|
||||||
ContextOpts: m.contextOpts(),
|
|
||||||
Input: m.Input(),
|
|
||||||
RunningInAutomation: m.RunningInAutomation,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't validate if we have a plan. Validation is normally harmless here,
|
|
||||||
// but validation requires interpolation, and `file()` function calls may
|
|
||||||
// not have the original files in the current execution context.
|
|
||||||
cliOpts.Validation = opts.Plan == nil
|
|
||||||
|
|
||||||
// If the backend supports CLI initialization, do it.
|
// If the backend supports CLI initialization, do it.
|
||||||
if cli, ok := b.(backend.CLI); ok {
|
if cli, ok := b.(backend.CLI); ok {
|
||||||
|
@ -151,6 +127,73 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
|
||||||
return local, nil
|
return local, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackendForPlan is similar to Backend, but uses backend settings that were
|
||||||
|
// stored in a plan.
|
||||||
|
//
|
||||||
|
// The current workspace name is also stored as part of the plan, and so this
|
||||||
|
// method will check that it matches the currently-selected workspace name
|
||||||
|
// and produce error diagnostics if not.
|
||||||
|
func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
f := backendinit.Backend(settings.Type)
|
||||||
|
if f == nil {
|
||||||
|
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), settings.Type))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
b := f()
|
||||||
|
|
||||||
|
schema := b.ConfigSchema()
|
||||||
|
configVal, err := settings.Config.Decode(schema.ImpliedType())
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(errwrap.Wrapf("saved backend configuration is invalid: {{err}}", err))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDiags := b.ValidateConfig(configVal)
|
||||||
|
diags = diags.Append(validateDiags)
|
||||||
|
if validateDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDiags := b.Configure(configVal)
|
||||||
|
diags = diags.Append(configureDiags)
|
||||||
|
|
||||||
|
// If the result of loading the backend is an enhanced backend,
|
||||||
|
// then return that as-is. This works even if b == nil (it will be !ok).
|
||||||
|
if enhanced, ok := b.(backend.Enhanced); ok {
|
||||||
|
return enhanced, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we'll wrap our state-only remote backend in the local backend
|
||||||
|
// to cause any operations to be run locally.
|
||||||
|
cliOpts := m.backendCLIOpts()
|
||||||
|
cliOpts.Validation = false // don't validate here in case config contains file(...) calls where the file doesn't exist
|
||||||
|
local := &backendlocal.Local{Backend: b}
|
||||||
|
if err := local.CLIInit(cliOpts); err != nil {
|
||||||
|
// Local backend should never fail, so this is always a bug.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return local, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// backendCLIOpts returns a backend.CLIOpts object that should be passed to
|
||||||
|
// a backend that supports local CLI operations.
|
||||||
|
func (m *Meta) backendCLIOpts() *backend.CLIOpts {
|
||||||
|
return &backend.CLIOpts{
|
||||||
|
CLI: m.Ui,
|
||||||
|
CLIColor: m.Colorize(),
|
||||||
|
ShowDiagnostics: m.showDiagnostics,
|
||||||
|
StatePath: m.statePath,
|
||||||
|
StateOutPath: m.stateOutPath,
|
||||||
|
StateBackupPath: m.backupPath,
|
||||||
|
ContextOpts: m.contextOpts(),
|
||||||
|
Input: m.Input(),
|
||||||
|
RunningInAutomation: m.RunningInAutomation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IsLocalBackend returns true if the backend is a local backend. We use this
|
// IsLocalBackend returns true if the backend is a local backend. We use this
|
||||||
// for some checks that require a remote backend.
|
// for some checks that require a remote backend.
|
||||||
func (m *Meta) IsLocalBackend(b backend.Backend) bool {
|
func (m *Meta) IsLocalBackend(b backend.Backend) bool {
|
||||||
|
@ -170,15 +213,24 @@ func (m *Meta) IsLocalBackend(b backend.Backend) bool {
|
||||||
// This prepares the operation. After calling this, the caller is expected
|
// This prepares the operation. After calling this, the caller is expected
|
||||||
// to modify fields of the operation such as Sequence to specify what will
|
// to modify fields of the operation such as Sequence to specify what will
|
||||||
// be called.
|
// be called.
|
||||||
func (m *Meta) Operation() *backend.Operation {
|
func (m *Meta) Operation(b backend.Backend) *backend.Operation {
|
||||||
|
schema := b.ConfigSchema()
|
||||||
|
workspace := m.Workspace()
|
||||||
|
planOutBackend, err := m.backendState.ForPlan(schema, workspace)
|
||||||
|
if err != nil {
|
||||||
|
// Always indicates an implementation error in practice, because
|
||||||
|
// errors here indicate invalid encoding of the backend configuration
|
||||||
|
// in memory, and we should always have validated that by the time
|
||||||
|
// we get here.
|
||||||
|
panic(fmt.Sprintf("failed to encode backend configuration for plan: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
return &backend.Operation{
|
return &backend.Operation{
|
||||||
PlanOutBackend: m.backendState,
|
PlanOutBackend: planOutBackend,
|
||||||
Parallelism: m.parallelism,
|
|
||||||
Targets: m.targets,
|
Targets: m.targets,
|
||||||
UIIn: m.UIInput(),
|
UIIn: m.UIInput(),
|
||||||
UIOut: m.Ui,
|
UIOut: m.Ui,
|
||||||
Variables: m.variables,
|
Workspace: workspace,
|
||||||
Workspace: m.Workspace(),
|
|
||||||
LockState: m.stateLock,
|
LockState: m.stateLock,
|
||||||
StateLockTimeout: m.stateLockTimeout,
|
StateLockTimeout: m.stateLockTimeout,
|
||||||
}
|
}
|
||||||
|
@ -242,9 +294,12 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
|
||||||
// backendFromConfig returns the initialized (not configured) backend
|
// backendFromConfig returns the initialized (not configured) backend
|
||||||
// directly from the config/state..
|
// directly from the config/state..
|
||||||
//
|
//
|
||||||
// This function handles any edge cases around backend config loading. For
|
// This function handles various edge cases around backend config loading. For
|
||||||
// example: legacy remote state, new config changes, backend type changes,
|
// example: new config changes, backend type changes, etc.
|
||||||
// etc.
|
//
|
||||||
|
// As of the 0.12 release it can no longer migrate from legacy remote state
|
||||||
|
// to backends, and will instead instruct users to use 0.11 or earlier as
|
||||||
|
// a stepping-stone to do that migration.
|
||||||
//
|
//
|
||||||
// This function may query the user for input unless input is disabled, in
|
// This function may query the user for input unless input is disabled, in
|
||||||
// which case this function will error.
|
// which case this function will error.
|
||||||
|
@ -288,16 +343,26 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// This giant switch statement covers all eight possible combinations
|
if !s.Remote.Empty() {
|
||||||
// of state settings between: configuring new backends, saved (previously-
|
// Legacy remote state is no longer supported. User must first
|
||||||
// configured) backends, and legacy remote state.
|
// migrate with Terraform 0.11 or earlier.
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Legacy remote state not supported",
|
||||||
|
"This working directory is configured for legacy remote state, which is no longer supported from Terraform v0.12 onwards. To migrate this environment, first run \"terraform init\" under a Terraform 0.11 release, and then upgrade Terraform again.",
|
||||||
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// This switch statement covers all the different combinations of
|
||||||
|
// configuring new backends, updating previously-configured backends, etc.
|
||||||
switch {
|
switch {
|
||||||
// No configuration set at all. Pure local state.
|
// No configuration set at all. Pure local state.
|
||||||
case c == nil && s.Remote.Empty() && s.Backend.Empty():
|
case c == nil && s.Backend.Empty():
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
// We're unsetting a backend (moving from backend => local)
|
// We're unsetting a backend (moving from backend => local)
|
||||||
case c == nil && s.Remote.Empty() && !s.Backend.Empty():
|
case c == nil && !s.Backend.Empty():
|
||||||
if !opts.Init {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
initReason := fmt.Sprintf(
|
||||||
"Unsetting the previously set backend %q",
|
"Unsetting the previously set backend %q",
|
||||||
|
@ -309,30 +374,8 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
|
|
||||||
return m.backend_c_r_S(c, cHash, sMgr, true)
|
return m.backend_c_r_S(c, cHash, sMgr, true)
|
||||||
|
|
||||||
// We have a legacy remote state configuration but no new backend config
|
|
||||||
case c == nil && !s.Remote.Empty() && s.Backend.Empty():
|
|
||||||
return m.backend_c_R_s(c, sMgr)
|
|
||||||
|
|
||||||
// We have a legacy remote state configuration simultaneously with a
|
|
||||||
// saved backend configuration while at the same time disabling backend
|
|
||||||
// configuration.
|
|
||||||
//
|
|
||||||
// This is a naturally impossible case: Terraform will never put you
|
|
||||||
// in this state, though it is theoretically possible through manual edits
|
|
||||||
case c == nil && !s.Remote.Empty() && !s.Backend.Empty():
|
|
||||||
if !opts.Init {
|
|
||||||
initReason := fmt.Sprintf(
|
|
||||||
"Unsetting the previously set backend %q",
|
|
||||||
s.Backend.Type)
|
|
||||||
m.backendInitRequired(initReason)
|
|
||||||
diags = diags.Append(errBackendInitRequired)
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.backend_c_R_S(c, cHash, sMgr)
|
|
||||||
|
|
||||||
// Configuring a backend for the first time.
|
// Configuring a backend for the first time.
|
||||||
case c != nil && s.Remote.Empty() && s.Backend.Empty():
|
case c != nil && s.Backend.Empty():
|
||||||
if !opts.Init {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
initReason := fmt.Sprintf(
|
||||||
"Initial configuration of the requested backend %q",
|
"Initial configuration of the requested backend %q",
|
||||||
|
@ -345,7 +388,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
return m.backend_C_r_s(c, cHash, sMgr)
|
return m.backend_C_r_s(c, cHash, sMgr)
|
||||||
|
|
||||||
// Potentially changing a backend configuration
|
// Potentially changing a backend configuration
|
||||||
case c != nil && s.Remote.Empty() && !s.Backend.Empty():
|
case c != nil && !s.Backend.Empty():
|
||||||
// If our configuration is the same, then we're just initializing
|
// If our configuration is the same, then we're just initializing
|
||||||
// a previously configured remote backend.
|
// a previously configured remote backend.
|
||||||
if !s.Backend.Empty() {
|
if !s.Backend.Empty() {
|
||||||
|
@ -369,237 +412,18 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
s.Backend.Hash, cHash)
|
s.Backend.Hash, cHash)
|
||||||
return m.backend_C_r_S_changed(c, cHash, sMgr, true)
|
return m.backend_C_r_S_changed(c, cHash, sMgr, true)
|
||||||
|
|
||||||
// Configuring a backend for the first time while having legacy
|
|
||||||
// remote state. This is very possible if a Terraform user configures
|
|
||||||
// a backend prior to ever running Terraform on an old state.
|
|
||||||
case c != nil && !s.Remote.Empty() && s.Backend.Empty():
|
|
||||||
if !opts.Init {
|
|
||||||
initReason := fmt.Sprintf(
|
|
||||||
"Initial configuration for backend %q",
|
|
||||||
c.Type)
|
|
||||||
m.backendInitRequired(initReason)
|
|
||||||
diags = diags.Append(errBackendInitRequired)
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.backend_C_R_s(c, sMgr)
|
|
||||||
|
|
||||||
// Configuring a backend with both a legacy remote state set
|
|
||||||
// and a pre-existing backend saved.
|
|
||||||
case c != nil && !s.Remote.Empty() && !s.Backend.Empty():
|
|
||||||
// If the hashes are the same, we have a legacy remote state with
|
|
||||||
// an unchanged stored backend state.
|
|
||||||
storedHash := s.Backend.Hash
|
|
||||||
if storedHash == cHash {
|
|
||||||
if !opts.Init {
|
|
||||||
initReason := fmt.Sprintf(
|
|
||||||
"Legacy remote state found with configured backend %q",
|
|
||||||
c.Type)
|
|
||||||
m.backendInitRequired(initReason)
|
|
||||||
diags = diags.Append(errBackendInitRequired)
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.backend_C_R_S_unchanged(c, sMgr, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.Init {
|
|
||||||
initReason := fmt.Sprintf(
|
|
||||||
"Reconfiguring the backend %q",
|
|
||||||
c.Type)
|
|
||||||
m.backendInitRequired(initReason)
|
|
||||||
diags = diags.Append(errBackendInitRequired)
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have change in all three
|
|
||||||
return m.backend_C_R_S_changed(c, sMgr)
|
|
||||||
default:
|
default:
|
||||||
// This should be impossible since all state possibilties are
|
|
||||||
// tested above, but we need a default case anyways and we should
|
|
||||||
// protect against the scenario where a case is somehow removed.
|
|
||||||
diags = diags.Append(fmt.Errorf(
|
diags = diags.Append(fmt.Errorf(
|
||||||
"Unhandled backend configuration state. This is a bug. Please\n"+
|
"Unhandled backend configuration state. This is a bug. Please\n"+
|
||||||
"report this error with the following information.\n\n"+
|
"report this error with the following information.\n\n"+
|
||||||
"Config Nil: %v\n"+
|
"Config Nil: %v\n"+
|
||||||
"Saved Backend Empty: %v\n"+
|
"Saved Backend Empty: %v\n",
|
||||||
"Legacy Remote Empty: %v\n",
|
c == nil, s.Backend.Empty(),
|
||||||
c == nil, s.Backend.Empty(), s.Remote.Empty(),
|
|
||||||
))
|
))
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// backendFromPlan loads the backend from a given plan file.
|
|
||||||
func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
|
|
||||||
if opts.Plan == nil {
|
|
||||||
panic("plan should not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
|
|
||||||
// We currently don't allow "-state" to be specified.
|
|
||||||
if m.statePath != "" {
|
|
||||||
diags = diags.Append(fmt.Errorf(
|
|
||||||
"State path cannot be specified with a plan file. The plan itself contains\n" +
|
|
||||||
"the state to use. If you wish to change that, please create a new plan\n" +
|
|
||||||
"and specify the state path when creating the plan.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
planBackend := opts.Plan.Backend
|
|
||||||
planState := opts.Plan.State
|
|
||||||
if planState == nil {
|
|
||||||
// The state can be nil, we just have to make it empty for the logic
|
|
||||||
// in this function.
|
|
||||||
planState = terraform.NewState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation only for non-local plans
|
|
||||||
local := planState.Remote.Empty() && planBackend.Empty()
|
|
||||||
if !local {
|
|
||||||
// We currently don't allow "-state-out" to be specified.
|
|
||||||
if m.stateOutPath != "" {
|
|
||||||
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanStateFlag)))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a stateOutPath, we must also specify it as the
|
|
||||||
// input path so we can check it properly. We restore it after this
|
|
||||||
// function exits.
|
|
||||||
original := m.statePath
|
|
||||||
m.statePath = m.stateOutPath
|
|
||||||
defer func() { m.statePath = original }()
|
|
||||||
|
|
||||||
var b backend.Backend
|
|
||||||
switch {
|
|
||||||
// No remote state at all, all local
|
|
||||||
case planState.Remote.Empty() && planBackend.Empty():
|
|
||||||
log.Printf("[INFO] command: initializing local backend from plan (not set)")
|
|
||||||
|
|
||||||
// Get the local backend
|
|
||||||
var backendDiags tfdiags.Diagnostics
|
|
||||||
b, backendDiags = m.Backend(&BackendOpts{ForceLocal: true})
|
|
||||||
diags = diags.Append(backendDiags)
|
|
||||||
|
|
||||||
// New backend configuration set
|
|
||||||
case planState.Remote.Empty() && !planBackend.Empty():
|
|
||||||
log.Printf(
|
|
||||||
"[INFO] command: initializing backend from plan: %s",
|
|
||||||
planBackend.Type)
|
|
||||||
|
|
||||||
var backendDiags tfdiags.Diagnostics
|
|
||||||
b, backendDiags = m.backendInitFromSaved(planBackend)
|
|
||||||
diags = diags.Append(backendDiags)
|
|
||||||
|
|
||||||
// Legacy remote state set
|
|
||||||
case !planState.Remote.Empty() && planBackend.Empty():
|
|
||||||
log.Printf(
|
|
||||||
"[INFO] command: initializing legacy remote backend from plan: %s",
|
|
||||||
planState.Remote.Type)
|
|
||||||
|
|
||||||
// Write our current state to an inmemory state just so that we
|
|
||||||
// have it in the format of state.State
|
|
||||||
inmem := &state.InmemState{}
|
|
||||||
inmem.WriteState(planState)
|
|
||||||
|
|
||||||
// Get the backend through the normal means of legacy state
|
|
||||||
var moreDiags tfdiags.Diagnostics
|
|
||||||
b, moreDiags = m.backend_c_R_s(nil, inmem)
|
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
|
|
||||||
// Both set, this can't happen in a plan.
|
|
||||||
case !planState.Remote.Empty() && !planBackend.Empty():
|
|
||||||
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanBoth)))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we had an error, return that
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
env := m.Workspace()
|
|
||||||
|
|
||||||
// Get the state so we can determine the effect of using this plan
|
|
||||||
realMgr, err := b.State(env)
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error reading state: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.stateLock {
|
|
||||||
stateLocker := clistate.NewLocker(context.Background(), m.stateLockTimeout, m.Ui, m.Colorize())
|
|
||||||
if err := stateLocker.Lock(realMgr, "backend from plan"); err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
defer stateLocker.Unlock(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := realMgr.RefreshState(); err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error reading state: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
real := realMgr.State()
|
|
||||||
if real != nil {
|
|
||||||
// If they're not the same lineage, don't allow this
|
|
||||||
if !real.SameLineage(planState) {
|
|
||||||
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendPlanLineageDiff)))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare ages
|
|
||||||
comp, err := real.CompareAges(planState)
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error comparing state ages for safety: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
switch comp {
|
|
||||||
case terraform.StateAgeEqual:
|
|
||||||
// State ages are equal, this is perfect
|
|
||||||
|
|
||||||
case terraform.StateAgeReceiverOlder:
|
|
||||||
// Real state is somehow older, this is okay.
|
|
||||||
|
|
||||||
case terraform.StateAgeReceiverNewer:
|
|
||||||
// If we have an older serial it is a problem but if we have a
|
|
||||||
// differing serial but are still identical, just let it through.
|
|
||||||
if real.Equal(planState) {
|
|
||||||
log.Printf("[WARN] command: state in plan has older serial, but Equal is true")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// The real state is newer, this is not allowed.
|
|
||||||
diags = diags.Append(fmt.Errorf(
|
|
||||||
strings.TrimSpace(errBackendPlanOlder),
|
|
||||||
planState.Serial, real.Serial,
|
|
||||||
))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the state
|
|
||||||
newState := opts.Plan.State.DeepCopy()
|
|
||||||
if newState != nil {
|
|
||||||
newState.Remote = nil
|
|
||||||
newState.Backend = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// realMgr locked above
|
|
||||||
if err := realMgr.WriteState(newState); err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error writing state: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
if err := realMgr.PersistState(); err != nil {
|
|
||||||
diags = diags.Append(fmt.Errorf("Error writing state: %s", err))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Backend Config Scenarios
|
// Backend Config Scenarios
|
||||||
//
|
//
|
||||||
|
@ -618,7 +442,7 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, tfdiags.Diag
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
// Unconfiguring a backend (moving from backend => local).
|
// Unconfiguring a backend (moving from backend => local).
|
||||||
func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
s := sMgr.State()
|
s := sMgr.State()
|
||||||
|
|
||||||
// Get the backend type for output
|
// Get the backend type for output
|
||||||
|
@ -673,7 +497,7 @@ func (m *Meta) backend_c_r_S(c *configs.Backend, cHash int, sMgr state.State, ou
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy remote state
|
// Legacy remote state
|
||||||
func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
||||||
|
@ -683,7 +507,7 @@ func (m *Meta) backend_c_R_s(c *configs.Backend, sMgr state.State) (backend.Back
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsetting backend, saved backend, legacy remote state
|
// Unsetting backend, saved backend, legacy remote state
|
||||||
func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
||||||
|
@ -693,7 +517,7 @@ func (m *Meta) backend_c_R_S(c *configs.Backend, cHash int, sMgr state.State) (b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuring a backend for the first time with legacy remote state.
|
// Configuring a backend for the first time with legacy remote state.
|
||||||
func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
||||||
|
@ -703,7 +527,7 @@ func (m *Meta) backend_C_R_s(c *configs.Backend, sMgr state.State) (backend.Back
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuring a backend for the first time.
|
// Configuring a backend for the first time.
|
||||||
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
// Get the backend
|
// Get the backend
|
||||||
b, configVal, diags := m.backendInitFromConfig(c)
|
b, configVal, diags := m.backendInitFromConfig(c)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
@ -717,7 +541,9 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (b
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaces, err := localB.States()
|
workspace := m.Workspace()
|
||||||
|
|
||||||
|
localState, err := localB.StateMgr(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
|
diags = diags.Append(fmt.Errorf(errBackendLocalRead, err))
|
||||||
return nil, diags
|
return nil, diags
|
||||||
|
@ -809,7 +635,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr state.State) (b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing a previously saved backend.
|
// Changing a previously saved backend.
|
||||||
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
if output {
|
if output {
|
||||||
// Notify the user
|
// Notify the user
|
||||||
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
|
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
|
||||||
|
@ -894,7 +720,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr state.S
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiailizing an unchanged saved backend
|
// Initiailizing an unchanged saved backend
|
||||||
func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
s := sMgr.State()
|
s := sMgr.State()
|
||||||
|
@ -948,7 +774,7 @@ func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiailizing a changed saved backend with legacy remote state.
|
// Initiailizing a changed saved backend with legacy remote state.
|
||||||
func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr state.State) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr *state.LocalState) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
||||||
|
@ -958,7 +784,7 @@ func (m *Meta) backend_C_R_S_changed(c *configs.Backend, sMgr state.State) (back
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiailizing an unchanged saved backend with legacy remote state.
|
// Initiailizing an unchanged saved backend with legacy remote state.
|
||||||
func (m *Meta) backend_C_R_S_unchanged(c *configs.Backend, sMgr state.State, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
func (m *Meta) backend_C_R_S_unchanged(c *configs.Backend, sMgr *state.LocalState, output bool) (backend.Backend, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
m.Ui.Error(strings.TrimSpace(errBackendLegacy) + "\n")
|
||||||
|
|
|
@ -11,6 +11,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
@ -43,8 +46,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
// We need to check what the named state status is. If we're converting
|
// We need to check what the named state status is. If we're converting
|
||||||
// from multi-state to single-state for example, we need to handle that.
|
// from multi-state to single-state for example, we need to handle that.
|
||||||
var oneSingle, twoSingle bool
|
var oneSingle, twoSingle bool
|
||||||
oneStates, err := opts.One.States()
|
oneStates, err := opts.One.Workspaces()
|
||||||
if err == backend.ErrNamedStatesNotSupported {
|
if err == backend.ErrWorkspacesNotSupported {
|
||||||
oneSingle = true
|
oneSingle = true
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
@ -53,8 +56,8 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
errMigrateLoadStates), opts.OneType, err)
|
errMigrateLoadStates), opts.OneType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = opts.Two.States()
|
_, err = opts.Two.Workspaces()
|
||||||
if err == backend.ErrNamedStatesNotSupported {
|
if err == backend.ErrWorkspacesNotSupported {
|
||||||
twoSingle = true
|
twoSingle = true
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
@ -144,7 +147,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read all the states
|
// Read all the states
|
||||||
oneStates, err := opts.One.States()
|
oneStates, err := opts.One.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(strings.TrimSpace(
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
errMigrateLoadStates), opts.OneType, err)
|
errMigrateLoadStates), opts.OneType, err)
|
||||||
|
@ -260,7 +263,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
// Single state to single state, assumed default state name.
|
// Single state to single state, assumed default state name.
|
||||||
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
stateOne, err := opts.One.State(opts.oneEnv)
|
stateOne, err := opts.One.StateMgr(opts.oneEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(strings.TrimSpace(
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
errMigrateSingleLoadDefault), opts.OneType, err)
|
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||||
|
@ -270,47 +273,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
errMigrateSingleLoadDefault), opts.OneType, err)
|
errMigrateSingleLoadDefault), opts.OneType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not migrate workspaces without state.
|
stateTwo, err := opts.Two.StateMgr(opts.twoEnv)
|
||||||
if stateOne.State() == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stateTwo, err := opts.Two.State(opts.twoEnv)
|
|
||||||
if err == backend.ErrDefaultStateNotSupported {
|
|
||||||
// If the backend doesn't support using the default state, we ask the user
|
|
||||||
// for a new name and migrate the default state to the given named state.
|
|
||||||
stateTwo, err = func() (state.State, error) {
|
|
||||||
name, err := m.UIInput().Input(&terraform.InputOpts{
|
|
||||||
Id: "new-state-name",
|
|
||||||
Query: fmt.Sprintf(
|
|
||||||
"[reset][bold][yellow]The %q backend configuration only allows "+
|
|
||||||
"named workspaces![reset]",
|
|
||||||
opts.TwoType),
|
|
||||||
Description: strings.TrimSpace(inputBackendNewWorkspaceName),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error asking for new state name: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the name of the target state.
|
|
||||||
opts.twoEnv = name
|
|
||||||
|
|
||||||
stateTwo, err := opts.Two.State(opts.twoEnv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the currently selected workspace is the default workspace, then set
|
|
||||||
// the named workspace as the new selected workspace.
|
|
||||||
if m.Workspace() == backend.DefaultStateName {
|
|
||||||
if err := m.SetWorkspace(opts.twoEnv); err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to set new workspace: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateTwo, nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(strings.TrimSpace(
|
return fmt.Errorf(strings.TrimSpace(
|
||||||
errMigrateSingleLoadDefault), opts.TwoType, err)
|
errMigrateSingleLoadDefault), opts.TwoType, err)
|
||||||
|
@ -328,8 +291,15 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
// no reason to migrate if the state is already there
|
// no reason to migrate if the state is already there
|
||||||
if one.Equal(two) {
|
if one.Equal(two) {
|
||||||
// Equal isn't identical; it doesn't check lineage.
|
// Equal isn't identical; it doesn't check lineage.
|
||||||
if one != nil && two != nil && one.Lineage == two.Lineage {
|
sm1, _ := stateOne.(statemgr.PersistentMeta)
|
||||||
return nil
|
sm2, _ := stateTwo.(statemgr.PersistentMeta)
|
||||||
|
if one != nil && two != nil {
|
||||||
|
if sm1 == nil || sm2 == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if sm1.StateSnapshotMeta().Lineage == sm2.StateSnapshotMeta().Lineage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,15 +333,6 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
|
||||||
two = stateTwo.State()
|
two = stateTwo.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the legacy remote state in both cases. If we're at the migration
|
|
||||||
// step then this won't be used anymore.
|
|
||||||
if one != nil {
|
|
||||||
one.Remote = nil
|
|
||||||
}
|
|
||||||
if two != nil {
|
|
||||||
two.Remote = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
|
var confirmFunc func(state.State, state.State, *backendMigrateOpts) (bool, error)
|
||||||
switch {
|
switch {
|
||||||
// No migration necessary
|
// No migration necessary
|
||||||
|
@ -453,14 +414,9 @@ func (m *Meta) backendMigrateNonEmptyConfirm(
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
// Helper to write the state
|
// Helper to write the state
|
||||||
saveHelper := func(n, path string, s *terraform.State) error {
|
saveHelper := func(n, path string, s *states.State) error {
|
||||||
f, err := os.Create(path)
|
mgr := statemgr.NewFilesystem(path)
|
||||||
if err != nil {
|
return mgr.WriteState(s)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return terraform.WriteState(s, f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the states
|
// Write the states
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,10 +7,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -129,51 +130,23 @@ func (m *Meta) Config(path string) (*config.Config, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plan returns the plan for the given path.
|
// PlanFile returns a reader for the plan file at the given path.
|
||||||
//
|
//
|
||||||
// This only has an effect if the path itself looks like a plan.
|
// If the return value and error are both nil, the given path exists but seems
|
||||||
// If error is nil and the plan is nil, then the path didn't look like
|
// to be a configuration directory instead.
|
||||||
// a plan.
|
|
||||||
//
|
//
|
||||||
// Error will be non-nil if path looks like a plan and loading the plan
|
// Error will be non-nil if path refers to something which looks like a plan
|
||||||
// failed.
|
// file and loading the file fails.
|
||||||
func (m *Meta) Plan(path string) (*terraform.Plan, error) {
|
func (m *Meta) PlanFile(path string) (*planfile.Reader, error) {
|
||||||
// Open the path no matter if its a directory or file
|
fi, err := os.Stat(path)
|
||||||
f, err := os.Open(path)
|
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"Failed to load Terraform configuration or plan: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat it so we can check if its a directory
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"Failed to load Terraform configuration or plan: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this path is a directory, then it can't be a plan. Not an error.
|
|
||||||
if fi.IsDir() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the plan
|
|
||||||
p, err := terraform.ReadPlan(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do a validation here that seems odd but if any plan is given,
|
if fi.IsDir() {
|
||||||
// we must not have set any extra variables. The plan itself contains
|
// Looks like a configuration directory.
|
||||||
// the variables and those aren't overwritten.
|
return nil, nil
|
||||||
if len(m.variableArgs.AllItems()) > 0 {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"You can't set variables with the '-var' or '-var-file' flag\n" +
|
|
||||||
"when you're applying a plan file. The variables used when\n" +
|
|
||||||
"the plan was created will be used. If you wish to use different\n" +
|
|
||||||
"variable values, create a new plan file.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
return planfile.Open(path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,15 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
stateStore, err := b.State(env)
|
stateStore, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -74,13 +75,15 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// This command uses a legacy shorthand syntax for the module path that
|
moduleAddr, addrDiags := addrs.ParseModuleInstanceStr(module)
|
||||||
// can't deal with keyed instances, so we'll just shim it for now and
|
diags = diags.Append(addrDiags)
|
||||||
// make the breaking change for this interface later.
|
if addrDiags.HasErrors() {
|
||||||
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
|
c.showDiagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
state := stateStore.State()
|
state := stateStore.State()
|
||||||
mod := state.ModuleByPath(modPath)
|
mod := state.Module(moduleAddr)
|
||||||
if mod == nil {
|
if mod == nil {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
"The module %s could not be found. There is nothing to output.",
|
"The module %s could not be found. There is nothing to output.",
|
||||||
|
@ -88,7 +91,12 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !jsonOutput && (state.Empty() || len(mod.Outputs) == 0) {
|
// TODO: We need to do an eval walk here to make sure all of the output
|
||||||
|
// values recorded in the state are up-to-date.
|
||||||
|
c.Ui.Error("output command not yet updated to do eval walk")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if !jsonOutput && (state.Empty() || len(mod.OutputValues) == 0) {
|
||||||
c.Ui.Error(
|
c.Ui.Error(
|
||||||
"The state file either has no outputs defined, or all the defined\n" +
|
"The state file either has no outputs defined, or all the defined\n" +
|
||||||
"outputs are empty. Please define an output in your configuration\n" +
|
"outputs are empty. Please define an output in your configuration\n" +
|
||||||
|
@ -101,7 +109,12 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
jsonOutputs, err := json.MarshalIndent(mod.Outputs, "", " ")
|
vals := make(map[string]cty.Value, len(mod.OutputValues))
|
||||||
|
for n, os := range mod.OutputValues {
|
||||||
|
vals[n] = os.Value
|
||||||
|
}
|
||||||
|
valsObj := cty.ObjectVal(vals)
|
||||||
|
jsonOutputs, err := ctyjson.Marshal(valsObj, valsObj.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -109,12 +122,12 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
c.Ui.Output(string(jsonOutputs))
|
c.Ui.Output(string(jsonOutputs))
|
||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
c.Ui.Output(outputsAsString(state, modPath, nil, false))
|
c.Ui.Output(outputsAsString(state, moduleAddr, nil, false))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := mod.Outputs[name]
|
os, ok := mod.OutputValues[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
"The output variable requested could not be found in the state\n" +
|
"The output variable requested could not be found in the state\n" +
|
||||||
|
@ -123,29 +136,18 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
"with new output variables until that command is run."))
|
"with new output variables until that command is run."))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
v := os.Value
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
jsonOutputs, err := json.MarshalIndent(v, "", " ")
|
jsonOutput, err := ctyjson.Marshal(v, v.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Output(string(jsonOutputs))
|
c.Ui.Output(string(jsonOutput))
|
||||||
} else {
|
} else {
|
||||||
switch output := v.Value.(type) {
|
c.Ui.Error("TODO: update output command to use the same value renderer as the console")
|
||||||
case string:
|
return 1
|
||||||
c.Ui.Output(output)
|
|
||||||
return 0
|
|
||||||
case []interface{}:
|
|
||||||
c.Ui.Output(formatListOutput("", "", output))
|
|
||||||
return 0
|
|
||||||
case map[string]interface{}:
|
|
||||||
c.Ui.Output(formatMapOutput("", "", output))
|
|
||||||
return 0
|
|
||||||
default:
|
|
||||||
c.Ui.Error(fmt.Sprintf("Unknown output type: %T", v.Type))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -6,24 +6,21 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOutput(t *testing.T) {
|
func TestOutput(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
})
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -50,28 +47,18 @@ func TestOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModuleOutput(t *testing.T) {
|
func TestModuleOutput(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
s.SetOutputValue(
|
||||||
Type: "string",
|
addrs.OutputValue{Name: "blah"}.Absolute(addrs.Module{"my_module"}.UnkeyedInstanceShim()),
|
||||||
},
|
cty.StringVal("tastatur"),
|
||||||
},
|
false,
|
||||||
},
|
)
|
||||||
{
|
})
|
||||||
Path: []string{"root", "my_module"},
|
|
||||||
Outputs: map[string]*terraform.OutputState{
|
|
||||||
"blah": {
|
|
||||||
Value: "tastatur",
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -100,28 +87,18 @@ func TestModuleOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModuleOutputs(t *testing.T) {
|
func TestModuleOutputs(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
s.SetOutputValue(
|
||||||
Type: "string",
|
addrs.OutputValue{Name: "blah"}.Absolute(addrs.Module{"my_module"}.UnkeyedInstanceShim()),
|
||||||
},
|
cty.StringVal("tastatur"),
|
||||||
},
|
false,
|
||||||
},
|
)
|
||||||
{
|
})
|
||||||
Path: []string{"root", "my_module"},
|
|
||||||
Outputs: map[string]*terraform.OutputState{
|
|
||||||
"blah": {
|
|
||||||
Value: "tastatur",
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -149,28 +126,21 @@ func TestModuleOutputs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_nestedListAndMap(t *testing.T) {
|
func TestOutput_nestedListAndMap(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.ListVal([]cty.Value{
|
||||||
Outputs: map[string]*terraform.OutputState{
|
cty.MapVal(map[string]cty.Value{
|
||||||
"foo": {
|
"key": cty.StringVal("value"),
|
||||||
Value: []interface{}{
|
"key2": cty.StringVal("value2"),
|
||||||
map[string]interface{}{
|
}),
|
||||||
"key": "value",
|
cty.MapVal(map[string]cty.Value{
|
||||||
"key2": "value2",
|
"key": cty.StringVal("value"),
|
||||||
},
|
}),
|
||||||
map[string]interface{}{
|
}),
|
||||||
"key": "value",
|
false,
|
||||||
},
|
)
|
||||||
},
|
})
|
||||||
Type: "list",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -196,19 +166,13 @@ func TestOutput_nestedListAndMap(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_json(t *testing.T) {
|
func TestOutput_json(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
})
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -236,15 +200,7 @@ func TestOutput_json(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_emptyOutputsErr(t *testing.T) {
|
func TestOutput_emptyOutputsErr(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.NewState()
|
||||||
Modules: []*terraform.ModuleState{
|
|
||||||
{
|
|
||||||
Path: []string{"root"},
|
|
||||||
Outputs: map[string]*terraform.OutputState{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -265,15 +221,7 @@ func TestOutput_emptyOutputsErr(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_jsonEmptyOutputs(t *testing.T) {
|
func TestOutput_jsonEmptyOutputs(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.NewState()
|
||||||
Modules: []*terraform.ModuleState{
|
|
||||||
{
|
|
||||||
Path: []string{"root"},
|
|
||||||
Outputs: map[string]*terraform.OutputState{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -301,20 +249,13 @@ func TestOutput_jsonEmptyOutputs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingModuleOutput(t *testing.T) {
|
func TestMissingModuleOutput(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
})
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -337,20 +278,13 @@ func TestMissingModuleOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_badVar(t *testing.T) {
|
func TestOutput_badVar(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
})
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -371,24 +305,18 @@ func TestOutput_badVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_blank(t *testing.T) {
|
func TestOutput_blank(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
s.SetOutputValue(
|
||||||
Type: "string",
|
addrs.OutputValue{Name: "name"}.Absolute(addrs.RootModuleInstance),
|
||||||
},
|
cty.StringVal("john-doe"),
|
||||||
"name": {
|
false,
|
||||||
Value: "john-doe",
|
)
|
||||||
Type: "string",
|
})
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -449,7 +377,7 @@ func TestOutput_noArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_noState(t *testing.T) {
|
func TestOutput_noState(t *testing.T) {
|
||||||
originalState := &terraform.State{}
|
originalState := states.NewState()
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -470,14 +398,7 @@ func TestOutput_noState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_noVars(t *testing.T) {
|
func TestOutput_noVars(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.NewState()
|
||||||
Modules: []*terraform.ModuleState{
|
|
||||||
{
|
|
||||||
Path: []string{"root"},
|
|
||||||
Outputs: map[string]*terraform.OutputState{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -499,19 +420,13 @@ func TestOutput_noVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutput_stateDefault(t *testing.T) {
|
func TestOutput_stateDefault(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetOutputValue(
|
||||||
{
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root"},
|
cty.StringVal("bar"),
|
||||||
Outputs: map[string]*terraform.OutputState{
|
false,
|
||||||
"foo": {
|
)
|
||||||
Value: "bar",
|
})
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the state file in a temporary directory with the
|
// Write the state file in a temporary directory with the
|
||||||
// default filename.
|
// default filename.
|
||||||
|
@ -522,7 +437,7 @@ func TestOutput_stateDefault(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
err = terraform.WriteState(originalState, f)
|
err = writeStateForTesting(originalState, f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
|
|
@ -53,37 +53,35 @@ func (c *PlanCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is a plan
|
// Check if the path is a plan, which is not permitted
|
||||||
plan, err := c.Plan(configPath)
|
planFileReader, err := c.PlanFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if plan != nil {
|
if planFileReader != nil {
|
||||||
// Disable refreshing no matter what since we only want to show the plan
|
c.showDiagnostics(tfdiags.Sourceless(
|
||||||
refresh = false
|
tfdiags.Error,
|
||||||
|
"Invalid configuration directory",
|
||||||
// Set the config path to empty for backend loading
|
fmt.Sprintf("Cannot pass a saved plan file to the 'terraform plan' command. To apply a saved plan, use: terraform apply %s", configPath),
|
||||||
configPath = ""
|
))
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
var backendConfig *configs.Backend
|
var backendConfig *configs.Backend
|
||||||
if plan == nil {
|
var configDiags tfdiags.Diagnostics
|
||||||
var configDiags tfdiags.Diagnostics
|
backendConfig, configDiags = c.loadBackendConfig(configPath)
|
||||||
backendConfig, configDiags = c.loadBackendConfig(configPath)
|
diags = diags.Append(configDiags)
|
||||||
diags = diags.Append(configDiags)
|
if configDiags.HasErrors() {
|
||||||
if configDiags.HasErrors() {
|
c.showDiagnostics(diags)
|
||||||
c.showDiagnostics(diags)
|
return 1
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, backendDiags := c.Backend(&BackendOpts{
|
b, backendDiags := c.Backend(&BackendOpts{
|
||||||
Config: backendConfig,
|
Config: backendConfig,
|
||||||
Plan: plan,
|
|
||||||
})
|
})
|
||||||
diags = diags.Append(backendDiags)
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
|
@ -97,10 +95,10 @@ func (c *PlanCommand) Run(args []string) int {
|
||||||
diags = nil
|
diags = nil
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(b)
|
||||||
opReq.Destroy = destroy
|
opReq.Destroy = destroy
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.Plan = plan
|
opReq.PlanRefresh = refresh
|
||||||
opReq.PlanOutPath = outPath
|
opReq.PlanOutPath = outPath
|
||||||
opReq.PlanRefresh = refresh
|
opReq.PlanRefresh = refresh
|
||||||
opReq.Type = backend.OperationTypePlan
|
opReq.Type = backend.OperationTypePlan
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
@ -83,9 +85,7 @@ func TestPlan_plan(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: testModule(t, "apply"),
|
|
||||||
})
|
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -107,22 +107,20 @@ func TestPlan_plan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlan_destroy(t *testing.T) {
|
func TestPlan_destroy(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
outPath := testTempFile(t)
|
outPath := testTempFile(t)
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
|
@ -232,21 +230,20 @@ func TestPlan_outPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlan_outPathNoChange(t *testing.T) {
|
func TestPlan_outPathNoChange(t *testing.T) {
|
||||||
originalState := &terraform.State{
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
td := testTempDir(t)
|
td := testTempDir(t)
|
||||||
|
@ -337,67 +334,6 @@ func TestPlan_outBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When using "-out" with a legacy remote state, the plan should encode
|
|
||||||
// the backend config
|
|
||||||
func TestPlan_outBackendLegacy(t *testing.T) {
|
|
||||||
// Create a temporary working directory that is empty
|
|
||||||
td := tempDir(t)
|
|
||||||
copy.CopyDir(testFixturePath("plan-out-backend-legacy"), td)
|
|
||||||
defer os.RemoveAll(td)
|
|
||||||
defer testChdir(t, td)()
|
|
||||||
|
|
||||||
// Our state
|
|
||||||
originalState := &terraform.State{
|
|
||||||
Modules: []*terraform.ModuleState{
|
|
||||||
&terraform.ModuleState{
|
|
||||||
Path: []string{"root"},
|
|
||||||
Resources: map[string]*terraform.ResourceState{
|
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
originalState.Init()
|
|
||||||
|
|
||||||
// Setup our legacy state
|
|
||||||
remoteState, srv := testRemoteState(t, originalState, 200)
|
|
||||||
defer srv.Close()
|
|
||||||
dataState := terraform.NewState()
|
|
||||||
dataState.Remote = remoteState
|
|
||||||
testStateFileRemote(t, dataState)
|
|
||||||
|
|
||||||
outPath := "foo"
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &PlanCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-out", outPath,
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
plan := testReadPlan(t, outPath)
|
|
||||||
if !plan.Diff.Empty() {
|
|
||||||
t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
|
|
||||||
}
|
|
||||||
|
|
||||||
if plan.State.Remote.Empty() {
|
|
||||||
t.Fatal("should have remote info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlan_refresh(t *testing.T) {
|
func TestPlan_refresh(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
@ -492,70 +428,6 @@ func TestPlan_stateDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlan_stateFuture(t *testing.T) {
|
|
||||||
originalState := testState()
|
|
||||||
originalState.TFVersion = "99.99.99"
|
|
||||||
statePath := testStateFile(t, originalState)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &PlanCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
testFixturePath("plan"),
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code == 0 {
|
|
||||||
t.Fatal("should fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !newState.Equal(originalState) {
|
|
||||||
t.Fatalf("bad: %#v", newState)
|
|
||||||
}
|
|
||||||
if newState.TFVersion != originalState.TFVersion {
|
|
||||||
t.Fatalf("bad: %#v", newState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlan_statePast(t *testing.T) {
|
|
||||||
originalState := testState()
|
|
||||||
originalState.TFVersion = "0.1.0"
|
|
||||||
statePath := testStateFile(t, originalState)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &PlanCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
testFixturePath("plan"),
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlan_validate(t *testing.T) {
|
func TestPlan_validate(t *testing.T) {
|
||||||
// This is triggered by not asking for input so we have to set this to false
|
// This is triggered by not asking for input so we have to set this to false
|
||||||
test = false
|
test = false
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
plugin "github.com/hashicorp/go-plugin"
|
plugin "github.com/hashicorp/go-plugin"
|
||||||
terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform"
|
|
||||||
tfplugin "github.com/hashicorp/terraform/plugin"
|
tfplugin "github.com/hashicorp/terraform/plugin"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -280,9 +279,11 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
|
||||||
|
|
||||||
func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory {
|
func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory {
|
||||||
return map[string]terraform.ResourceProviderFactory{
|
return map[string]terraform.ResourceProviderFactory{
|
||||||
"terraform": func() (terraform.ResourceProvider, error) {
|
// FIXME: Re-enable this once the internal provider system is updated
|
||||||
return terraformProvider.Provider(), nil
|
// for the new provider interface.
|
||||||
},
|
//"terraform": func() (terraform.ResourceProvider, error) {
|
||||||
|
// return terraformProvider.Provider(), nil
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (c *ProvidersCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (c *RefreshCommand) Run(args []string) int {
|
||||||
diags = nil
|
diags = nil
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation()
|
opReq := c.Operation(b)
|
||||||
opReq.Type = backend.OperationTypeRefresh
|
opReq.Type = backend.OperationTypeRefresh
|
||||||
opReq.ConfigDir = configPath
|
opReq.ConfigDir = configPath
|
||||||
opReq.ConfigLoader, err = c.initConfigLoader()
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
|
|
|
@ -2,18 +2,19 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
|
||||||
"github.com/hashicorp/terraform/state"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/hashicorp/terraform/version"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRefresh(t *testing.T) {
|
func TestRefresh(t *testing.T) {
|
||||||
|
@ -185,263 +186,162 @@ func TestRefresh_cwd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_defaultState(t *testing.T) {
|
func TestRefresh_defaultState(t *testing.T) {
|
||||||
originalState := testState()
|
t.Fatal("not yet updated for new provider types")
|
||||||
|
/*
|
||||||
|
originalState := testState()
|
||||||
|
|
||||||
// Write the state file in a temporary directory with the
|
// Write the state file in a temporary directory with the
|
||||||
// default filename.
|
// default filename.
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
localState := &state.LocalState{Path: statePath}
|
localState := &state.LocalState{Path: statePath}
|
||||||
if err := localState.RefreshState(); err != nil {
|
if err := localState.RefreshState(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := localState.State()
|
s := localState.State()
|
||||||
if s == nil {
|
if s == nil {
|
||||||
t.Fatal("empty test state")
|
t.Fatal("empty test state")
|
||||||
}
|
}
|
||||||
serial := s.Serial
|
serial := s.Serial
|
||||||
|
|
||||||
// Change to that directory
|
// Change to that directory
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
if err := os.Chdir(filepath.Dir(statePath)); err != nil {
|
if err := os.Chdir(filepath.Dir(statePath)); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
defer os.Chdir(cwd)
|
defer os.Chdir(cwd)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RefreshCommand{
|
c := &RefreshCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RefreshFn = nil
|
p.RefreshFn = nil
|
||||||
p.RefreshReturn = newInstanceState("yes")
|
p.RefreshReturn = newInstanceState("yes")
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
testFixturePath("refresh"),
|
testFixturePath("refresh"),
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.RefreshCalled {
|
if !p.RefreshCalled {
|
||||||
t.Fatal("refresh should be called")
|
t.Fatal("refresh should be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := testStateRead(t, statePath)
|
newState := testStateRead(t, statePath)
|
||||||
|
|
||||||
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
|
||||||
expected := p.RefreshReturn
|
expected := p.RefreshReturn
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Logf("expected:\n%#v", expected)
|
t.Logf("expected:\n%#v", expected)
|
||||||
t.Fatalf("bad:\n%#v", actual)
|
t.Fatalf("bad:\n%#v", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newState.Serial <= serial {
|
backupState := testStateRead(t, statePath+DefaultBackupExtension)
|
||||||
t.Fatalf("serial not incremented during refresh. previous:%d, current:%d", serial, newState.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
backupState := testStateRead(t, statePath+DefaultBackupExtension)
|
actual = backupState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
|
||||||
|
expected = originalState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
|
||||||
actual = backupState.RootModule().Resources["test_instance.foo"].Primary
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
expected = originalState.RootModule().Resources["test_instance.foo"].Primary
|
t.Fatalf("bad: %#v", actual)
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
}
|
||||||
t.Fatalf("bad: %#v", actual)
|
*/
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRefresh_futureState(t *testing.T) {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if err := os.Chdir(testFixturePath("refresh")); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
defer os.Chdir(cwd)
|
|
||||||
|
|
||||||
state := testState()
|
|
||||||
state.TFVersion = "99.99.99"
|
|
||||||
statePath := testStateFile(t, state)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &RefreshCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code == 0 {
|
|
||||||
t.Fatal("should fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.RefreshCalled {
|
|
||||||
t.Fatal("refresh should not be called")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(newState.String())
|
|
||||||
expected := strings.TrimSpace(state.String())
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRefresh_pastState(t *testing.T) {
|
|
||||||
state := testState()
|
|
||||||
state.TFVersion = "0.1.0"
|
|
||||||
statePath := testStateFile(t, state)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &RefreshCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
p.RefreshFn = nil
|
|
||||||
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
testFixturePath("refresh"),
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.RefreshCalled {
|
|
||||||
t.Fatal("refresh should be called")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(newState.String())
|
|
||||||
expected := strings.TrimSpace(testRefreshStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newState.TFVersion != version.Version {
|
|
||||||
t.Fatalf("bad:\n\n%s", newState.TFVersion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_outPath(t *testing.T) {
|
func TestRefresh_outPath(t *testing.T) {
|
||||||
state := testState()
|
t.Fatal("not yet updated for new provider types")
|
||||||
statePath := testStateFile(t, state)
|
/*
|
||||||
|
state := testState()
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
// Output path
|
// Output path
|
||||||
outf, err := ioutil.TempFile(testingDir, "tf")
|
outf, err := ioutil.TempFile(testingDir, "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
outPath := outf.Name()
|
outPath := outf.Name()
|
||||||
outf.Close()
|
outf.Close()
|
||||||
os.Remove(outPath)
|
os.Remove(outPath)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RefreshCommand{
|
c := &RefreshCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RefreshFn = nil
|
p.RefreshFn = nil
|
||||||
p.RefreshReturn = newInstanceState("yes")
|
p.RefreshReturn = newInstanceState("yes")
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"-state-out", outPath,
|
"-state-out", outPath,
|
||||||
testFixturePath("refresh"),
|
testFixturePath("refresh"),
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
f, err := os.Open(statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
newState, err := terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(newState, state) {
|
if !reflect.DeepEqual(newState, state) {
|
||||||
t.Fatalf("bad: %#v", newState)
|
t.Fatalf("bad: %#v", newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.Open(outPath)
|
f, err = os.Open(outPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err = terraform.ReadState(f)
|
newState, err = terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
||||||
expected := p.RefreshReturn
|
expected := p.RefreshReturn
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.Open(outPath + DefaultBackupExtension)
|
f, err = os.Open(outPath + DefaultBackupExtension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
backupState, err := terraform.ReadState(f)
|
backupState, err := terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualStr := strings.TrimSpace(backupState.String())
|
actualStr := strings.TrimSpace(backupState.String())
|
||||||
expectedStr := strings.TrimSpace(state.String())
|
expectedStr := strings.TrimSpace(state.String())
|
||||||
if actualStr != expectedStr {
|
if actualStr != expectedStr {
|
||||||
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
|
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_var(t *testing.T) {
|
func TestRefresh_var(t *testing.T) {
|
||||||
|
@ -582,175 +482,181 @@ func TestRefresh_varsUnset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_backup(t *testing.T) {
|
func TestRefresh_backup(t *testing.T) {
|
||||||
state := testState()
|
t.Fatal("not yet updated for new provider types")
|
||||||
statePath := testStateFile(t, state)
|
/*
|
||||||
|
state := testState()
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
// Output path
|
// Output path
|
||||||
outf, err := ioutil.TempFile(testingDir, "tf")
|
outf, err := ioutil.TempFile(testingDir, "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
outPath := outf.Name()
|
outPath := outf.Name()
|
||||||
outf.Close()
|
outf.Close()
|
||||||
os.Remove(outPath)
|
os.Remove(outPath)
|
||||||
|
|
||||||
// Backup path
|
// Backup path
|
||||||
backupf, err := ioutil.TempFile(testingDir, "tf")
|
backupf, err := ioutil.TempFile(testingDir, "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
backupPath := backupf.Name()
|
backupPath := backupf.Name()
|
||||||
backupf.Close()
|
backupf.Close()
|
||||||
os.Remove(backupPath)
|
os.Remove(backupPath)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RefreshCommand{
|
c := &RefreshCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RefreshFn = nil
|
p.RefreshFn = nil
|
||||||
p.RefreshReturn = newInstanceState("yes")
|
p.RefreshReturn = newInstanceState("yes")
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"-state-out", outPath,
|
"-state-out", outPath,
|
||||||
"-backup", backupPath,
|
"-backup", backupPath,
|
||||||
testFixturePath("refresh"),
|
testFixturePath("refresh"),
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
f, err := os.Open(statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
newState, err := terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(newState, state) {
|
if !reflect.DeepEqual(newState, state) {
|
||||||
t.Fatalf("bad: %#v", newState)
|
t.Fatalf("bad: %#v", newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.Open(outPath)
|
f, err = os.Open(outPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err = terraform.ReadState(f)
|
newState, err = terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
||||||
expected := p.RefreshReturn
|
expected := p.RefreshReturn
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.Open(backupPath)
|
f, err = os.Open(backupPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
backupState, err := terraform.ReadState(f)
|
backupState, err := terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualStr := strings.TrimSpace(backupState.String())
|
actualStr := strings.TrimSpace(backupState.String())
|
||||||
expectedStr := strings.TrimSpace(state.String())
|
expectedStr := strings.TrimSpace(state.String())
|
||||||
if actualStr != expectedStr {
|
if actualStr != expectedStr {
|
||||||
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
|
t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_disableBackup(t *testing.T) {
|
func TestRefresh_disableBackup(t *testing.T) {
|
||||||
state := testState()
|
t.Fatal("not yet updated for new provider types")
|
||||||
statePath := testStateFile(t, state)
|
/*
|
||||||
|
state := testState()
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
// Output path
|
// Output path
|
||||||
outf, err := ioutil.TempFile(testingDir, "tf")
|
outf, err := ioutil.TempFile(testingDir, "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
outPath := outf.Name()
|
outPath := outf.Name()
|
||||||
outf.Close()
|
outf.Close()
|
||||||
os.Remove(outPath)
|
os.Remove(outPath)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &RefreshCommand{
|
c := &RefreshCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RefreshFn = nil
|
p.RefreshFn = nil
|
||||||
p.RefreshReturn = newInstanceState("yes")
|
p.RefreshReturn = newInstanceState("yes")
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"-state-out", outPath,
|
"-state-out", outPath,
|
||||||
"-backup", "-",
|
"-backup", "-",
|
||||||
testFixturePath("refresh"),
|
testFixturePath("refresh"),
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(statePath)
|
f, err := os.Open(statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err := terraform.ReadState(f)
|
newState, err := terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(newState, state) {
|
if !reflect.DeepEqual(newState, state) {
|
||||||
t.Fatalf("bad: %#v", newState)
|
t.Fatalf("bad: %#v", newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err = os.Open(outPath)
|
f, err = os.Open(outPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err = terraform.ReadState(f)
|
newState, err = terraform.ReadState(f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
actual := newState.RootModule().Resources["test_instance.foo"].Primary
|
||||||
expected := p.RefreshReturn
|
expected := p.RefreshReturn
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is no backup
|
// Ensure there is no backup
|
||||||
_, err = os.Stat(outPath + DefaultBackupExtension)
|
_, err = os.Stat(outPath + DefaultBackupExtension)
|
||||||
if err == nil || !os.IsNotExist(err) {
|
if err == nil || !os.IsNotExist(err) {
|
||||||
t.Fatalf("backup should not exist")
|
t.Fatalf("backup should not exist")
|
||||||
}
|
}
|
||||||
_, err = os.Stat("-")
|
_, err = os.Stat("-")
|
||||||
if err == nil || !os.IsNotExist(err) {
|
if err == nil || !os.IsNotExist(err) {
|
||||||
t.Fatalf("backup should not exist")
|
t.Fatalf("backup should not exist")
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefresh_displaysOutputs(t *testing.T) {
|
func TestRefresh_displaysOutputs(t *testing.T) {
|
||||||
|
@ -782,17 +688,21 @@ func TestRefresh_displaysOutputs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When creating an InstaneState for direct comparison to one contained in
|
// newInstanceState creates a new states.ResourceInstanceObjectSrc with the
|
||||||
// terraform.State, all fields must be initialized (duplicating the
|
// given value for its single id attribute. It is named newInstanceState for
|
||||||
// InstanceState.init() method)
|
// historical reasons, because it was originally written for the poorly-named
|
||||||
func newInstanceState(id string) *terraform.InstanceState {
|
// terraform.InstanceState type.
|
||||||
return &terraform.InstanceState{
|
func newInstanceState(id string) *states.ResourceInstanceObjectSrc {
|
||||||
ID: id,
|
attrs := map[string]interface{}{
|
||||||
Attributes: make(map[string]string),
|
"id": id,
|
||||||
Ephemeral: terraform.EphemeralState{
|
}
|
||||||
ConnInfo: make(map[string]string),
|
attrsJSON, err := json.Marshal(attrs)
|
||||||
},
|
if err != nil {
|
||||||
Meta: make(map[string]interface{}),
|
panic(fmt.Sprintf("failed to marshal attributes: %s", err)) // should never happen
|
||||||
|
}
|
||||||
|
return &states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: attrsJSON,
|
||||||
|
Status: states.ObjectReady,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/plans/planfile"
|
||||||
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShowCommand is a Command implementation that reads and outputs the
|
// ShowCommand is a Command implementation that reads and outputs the
|
||||||
|
@ -42,31 +46,30 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
|
|
||||||
var planErr, stateErr error
|
var planErr, stateErr error
|
||||||
var path string
|
var path string
|
||||||
var plan *terraform.Plan
|
var plan *plans.Plan
|
||||||
var state *terraform.State
|
var state *states.State
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
path = args[0]
|
path = args[0]
|
||||||
f, err := os.Open(path)
|
pr, err := planfile.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
|
f, err := os.Open(path)
|
||||||
return 1
|
if err != nil {
|
||||||
}
|
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
plan, err = terraform.ReadPlan(f)
|
|
||||||
if err != nil {
|
|
||||||
if _, err := f.Seek(0, 0); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
plan = nil
|
var stateFile *statefile.File
|
||||||
planErr = err
|
stateFile, err = statefile.Read(f)
|
||||||
}
|
|
||||||
if plan == nil {
|
|
||||||
state, err = terraform.ReadState(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stateErr = err
|
stateErr = err
|
||||||
|
} else {
|
||||||
|
state = stateFile.State
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plan, err = pr.ReadPlan()
|
||||||
|
if err != nil {
|
||||||
|
planErr = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,7 +83,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
stateStore, err := b.State(env)
|
stateStore, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -110,7 +113,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if plan != nil {
|
if plan != nil {
|
||||||
dispPlan := format.NewPlan(plan)
|
dispPlan := format.NewPlan(plan.Changes)
|
||||||
c.Ui.Output(dispPlan.Format(c.Colorize()))
|
c.Ui.Output(dispPlan.Format(c.Colorize()))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,8 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,9 +64,7 @@ func TestShow_noArgsNoState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_plan(t *testing.T) {
|
func TestShow_plan(t *testing.T) {
|
||||||
planPath := testPlanFile(t, &terraform.Plan{
|
planPath := testPlanFileNoop(t)
|
||||||
Config: configs.NewEmptyConfig(),
|
|
||||||
})
|
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
|
@ -88,36 +82,6 @@ func TestShow_plan(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_noArgsRemoteState(t *testing.T) {
|
|
||||||
tmp, cwd := testCwd(t)
|
|
||||||
defer testFixCwd(t, tmp, cwd)
|
|
||||||
|
|
||||||
// Create some legacy remote state
|
|
||||||
legacyState := testState()
|
|
||||||
_, srv := testRemoteState(t, legacyState, 200)
|
|
||||||
defer srv.Close()
|
|
||||||
testStateFileRemote(t, legacyState)
|
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &ShowCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "test_instance.foo"
|
|
||||||
actual := ui.OutputWriter.String()
|
|
||||||
if !strings.Contains(actual, expected) {
|
|
||||||
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShow_state(t *testing.T) {
|
func TestShow_state(t *testing.T) {
|
||||||
originalState := testState()
|
originalState := testState()
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("state list")
|
cmdFlags := c.Meta.flagSet("state list")
|
||||||
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
||||||
lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.")
|
//lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.")
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return cli.RunResultHelp
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
|
@ -38,7 +37,7 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
|
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
// Get the state
|
// Get the state
|
||||||
state, err := b.State(env)
|
state, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -55,7 +54,11 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := &terraform.StateFilter{State: stateReal}
|
// FIXME: update this for the new state types
|
||||||
|
c.Ui.Error("state list command not yet updated for new state types")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
/*filter := &terraform.StateFilter{State: stateReal}
|
||||||
results, err := filter.Filter(args...)
|
results, err := filter.Filter(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
||||||
|
@ -68,7 +71,7 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
c.Ui.Output(result.Address)
|
c.Ui.Output(result.Address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
backendLocal "github.com/hashicorp/terraform/backend/local"
|
backendLocal "github.com/hashicorp/terraform/backend/local"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,9 +27,7 @@ func (c *StateMeta) State() (state.State, error) {
|
||||||
|
|
||||||
// use the specified state
|
// use the specified state
|
||||||
if c.statePath != "" {
|
if c.statePath != "" {
|
||||||
realState = &state.LocalState{
|
realState = statemgr.NewFilesystem(c.statePath)
|
||||||
Path: c.statePath,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, backendDiags := c.Backend(nil)
|
b, backendDiags := c.Backend(nil)
|
||||||
|
@ -36,9 +35,9 @@ func (c *StateMeta) State() (state.State, error) {
|
||||||
return nil, backendDiags.Err()
|
return nil, backendDiags.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Workspace()
|
workspace := c.Workspace()
|
||||||
// Get the state
|
// Get the state
|
||||||
s, err := b.State(env)
|
s, err := b.StateMgr(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -49,8 +48,8 @@ func (c *StateMeta) State() (state.State, error) {
|
||||||
// This should never fail
|
// This should never fail
|
||||||
panic(backendDiags.Err())
|
panic(backendDiags.Err())
|
||||||
}
|
}
|
||||||
localB := localRaw.(*backendLocal.Local)
|
localB := localRaw.(*backendlocal.Local)
|
||||||
_, stateOutPath, _ = localB.StatePaths(env)
|
_, stateOutPath, _ = localB.StatePaths(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,10 +69,10 @@ func (c *StateMeta) State() (state.State, error) {
|
||||||
DefaultBackupExtension)
|
DefaultBackupExtension)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap it for backups
|
// If the backend is local (which it should always be, given our asserting
|
||||||
realState = &state.BackupState{
|
// of it above) we can now enable backups for it.
|
||||||
Real: realState,
|
if lb, ok := realState.(*statemgr.Filesystem); ok {
|
||||||
Path: backupPath,
|
lb.SetBackupPath(backupPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return realState, nil
|
return realState, nil
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
@ -74,59 +75,62 @@ func (c *StateMvCommand) Run(args []string) int {
|
||||||
|
|
||||||
stateToReal = stateTo.State()
|
stateToReal = stateTo.State()
|
||||||
if stateToReal == nil {
|
if stateToReal == nil {
|
||||||
stateToReal = terraform.NewState()
|
stateToReal = states.NewState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter what we're moving
|
c.Ui.Error("state mv command not yet updated for new state types")
|
||||||
filter := &terraform.StateFilter{State: stateFromReal}
|
/*
|
||||||
results, err := filter.Filter(args[0])
|
// Filter what we're moving
|
||||||
if err != nil {
|
filter := &terraform.StateFilter{State: stateFromReal}
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
results, err := filter.Filter(args[0])
|
||||||
return cli.RunResultHelp
|
if err != nil {
|
||||||
}
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
if len(results) == 0 {
|
return cli.RunResultHelp
|
||||||
c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0]))
|
}
|
||||||
return 1
|
if len(results) == 0 {
|
||||||
}
|
c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0]))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// Get the item to add to the state
|
// Get the item to add to the state
|
||||||
add := c.addableResult(results)
|
add := c.addableResult(results)
|
||||||
|
|
||||||
// Do the actual move
|
// Do the actual move
|
||||||
if err := stateFromReal.Remove(args[0]); err != nil {
|
if err := stateFromReal.Remove(args[0]); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateToReal.Add(args[0], args[1], add); err != nil {
|
if err := stateToReal.Add(args[0], args[1], add); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the new state
|
// Write the new state
|
||||||
if err := stateTo.WriteState(stateToReal); err != nil {
|
if err := stateTo.WriteState(stateToReal); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stateTo.PersistState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the old state if it is different
|
|
||||||
if stateTo != stateFrom {
|
|
||||||
if err := stateFrom.WriteState(stateFromReal); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateFrom.PersistState(); err != nil {
|
if err := stateTo.PersistState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Write the old state if it is different
|
||||||
|
if stateTo != stateFrom {
|
||||||
|
if err := stateFrom.WriteState(stateFromReal); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stateFrom.PersistState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf(
|
c.Ui.Output(fmt.Sprintf(
|
||||||
"Moved %s to %s", args[0], args[1]))
|
"Moved %s to %s", args[0], args[1]))
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateMv(t *testing.T) {
|
func TestStateMv(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.baz": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "baz",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -84,37 +83,32 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
|
||||||
|
|
||||||
backupPath := filepath.Join(td, "backup")
|
backupPath := filepath.Join(td, "backup")
|
||||||
|
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.baz": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "baz",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
// init our backend
|
// init our backend
|
||||||
|
@ -162,37 +156,32 @@ func TestStateMv_backupExplicit(t *testing.T) {
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
backupPath := filepath.Join(td, "backup")
|
backupPath := filepath.Join(td, "backup")
|
||||||
|
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.baz": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "baz",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -224,26 +213,20 @@ func TestStateMv_backupExplicit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateMv_stateOutNew(t *testing.T) {
|
func TestStateMv_stateOutNew(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
stateOutPath := statePath + ".out"
|
stateOutPath := statePath + ".out"
|
||||||
|
|
||||||
|
@ -281,44 +264,36 @@ func TestStateMv_stateOutNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateMv_stateOutExisting(t *testing.T) {
|
func TestStateMv_stateOutExisting(t *testing.T) {
|
||||||
stateSrc := &terraform.State{
|
stateSrc := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, stateSrc)
|
statePath := testStateFile(t, stateSrc)
|
||||||
|
|
||||||
stateDst := &terraform.State{
|
stateDst := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.qux": &terraform.ResourceState{
|
Name: "qux",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
stateOutPath := testStateFile(t, stateDst)
|
stateOutPath := testStateFile(t, stateDst)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -382,48 +357,44 @@ func TestStateMv_noState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateMv_stateOutNew_count(t *testing.T) {
|
func TestStateMv_stateOutNew_count(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo.0": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "foo",
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.1": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.bar": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
stateOutPath := statePath + ".out"
|
stateOutPath := statePath + ".out"
|
||||||
|
|
||||||
|
@ -463,147 +434,35 @@ func TestStateMv_stateOutNew_count(t *testing.T) {
|
||||||
// Modules with more than 10 resources were sorted lexically, causing the
|
// Modules with more than 10 resources were sorted lexically, causing the
|
||||||
// indexes in the new location to change.
|
// indexes in the new location to change.
|
||||||
func TestStateMv_stateOutNew_largeCount(t *testing.T) {
|
func TestStateMv_stateOutNew_largeCount(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
// test_instance.foo has 11 instances, all the same except for their ids
|
||||||
&terraform.ModuleState{
|
for i := 0; i < 11; i++ {
|
||||||
Path: []string{"root"},
|
s.SetResourceInstanceCurrent(
|
||||||
Resources: map[string]*terraform.ResourceState{
|
addrs.Resource{
|
||||||
"test_instance.foo.0": &terraform.ResourceState{
|
Mode: addrs.ManagedResourceMode,
|
||||||
Type: "test_instance",
|
Type: "test_instance",
|
||||||
Primary: &terraform.InstanceState{
|
Name: "foo",
|
||||||
ID: "foo0",
|
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance),
|
||||||
Attributes: map[string]string{
|
&states.ResourceInstanceObjectSrc{
|
||||||
"foo": "value",
|
AttrsJSON: []byte(fmt.Sprintf(`{"id":"foo%d","foo":"value","bar":"value"}`, i)),
|
||||||
"bar": "value",
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.1": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo1",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.2": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo2",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.3": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo3",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.4": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo4",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.5": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo5",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.6": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo6",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.7": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo7",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.8": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo8",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.9": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo9",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.foo.10": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo10",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.bar": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
stateOutPath := statePath + ".out"
|
stateOutPath := statePath + ".out"
|
||||||
|
|
||||||
|
@ -641,51 +500,32 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{},
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("child1", addrs.NoKey)),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
&terraform.ModuleState{
|
)
|
||||||
Path: []string{"root", "foo"},
|
s.SetResourceInstanceCurrent(
|
||||||
Resources: map[string]*terraform.ResourceState{},
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("child2", addrs.NoKey)),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
&terraform.ModuleState{
|
)
|
||||||
Path: []string{"root", "foo", "child1"},
|
})
|
||||||
Resources: map[string]*terraform.ResourceState{
|
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
&terraform.ModuleState{
|
|
||||||
Path: []string{"root", "foo", "child2"},
|
|
||||||
Resources: map[string]*terraform.ResourceState{
|
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
stateOutPath := statePath + ".out"
|
stateOutPath := statePath + ".out"
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,7 +34,7 @@ func (c *StatePullCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -54,13 +52,19 @@ func (c *StatePullCommand) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
c.Ui.Error("state pull not yet updated for new state types")
|
||||||
if err := terraform.WriteState(s, &buf); err != nil {
|
return 1
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
||||||
return 1
|
/*
|
||||||
}
|
var buf bytes.Buffer
|
||||||
|
if err := terraform.WriteState(s, &buf); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Output(buf.String())
|
||||||
|
*/
|
||||||
|
|
||||||
c.Ui.Output(buf.String())
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ func TestStatePull(t *testing.T) {
|
||||||
|
|
||||||
// Create some legacy remote state
|
// Create some legacy remote state
|
||||||
legacyState := testState()
|
legacyState := testState()
|
||||||
_, srv := testRemoteState(t, legacyState, 200)
|
backendState, srv := testRemoteState(t, legacyState, 200)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
testStateFileRemote(t, legacyState)
|
testStateFileRemote(t, backendState)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,80 +31,86 @@ func (c *StatePushCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine our reader for the input state. This is the filepath
|
c.Ui.Error("state push not yet updated for new state types")
|
||||||
// or stdin if "-" is given.
|
return 1
|
||||||
var r io.Reader = os.Stdin
|
|
||||||
if args[0] != "-" {
|
/*
|
||||||
f, err := os.Open(args[0])
|
// Determine our reader for the input state. This is the filepath
|
||||||
|
// or stdin if "-" is given.
|
||||||
|
var r io.Reader = os.Stdin
|
||||||
|
if args[0] != "-" {
|
||||||
|
f, err := os.Open(args[0])
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we don't need to defer a Close here because we do a close
|
||||||
|
// automatically below directly after the read.
|
||||||
|
|
||||||
|
r = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the state
|
||||||
|
sourceState, err := terraform.ReadState(r)
|
||||||
|
if c, ok := r.(io.Closer); ok {
|
||||||
|
// Close the reader if possible right now since we're done with it.
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we don't need to defer a Close here because we do a close
|
// Load the backend
|
||||||
// automatically below directly after the read.
|
b, backendDiags := c.Backend(nil)
|
||||||
|
if backendDiags.HasErrors() {
|
||||||
r = f
|
c.showDiagnostics(backendDiags)
|
||||||
}
|
|
||||||
|
|
||||||
// Read the state
|
|
||||||
sourceState, err := terraform.ReadState(r)
|
|
||||||
if c, ok := r.(io.Closer); ok {
|
|
||||||
// Close the reader if possible right now since we're done with it.
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the backend
|
|
||||||
b, backendDiags := c.Backend(nil)
|
|
||||||
if backendDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(backendDiags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the state
|
|
||||||
env := c.Workspace()
|
|
||||||
state, err := b.State(env)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := state.RefreshState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
dstState := state.State()
|
|
||||||
|
|
||||||
// If we're not forcing, then perform safety checks
|
|
||||||
if !flagForce && !dstState.Empty() {
|
|
||||||
if !dstState.SameLineage(sourceState) {
|
|
||||||
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
age, err := dstState.CompareAges(sourceState)
|
// Get the state
|
||||||
|
env := c.Workspace()
|
||||||
|
state, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if age == terraform.StateAgeReceiverNewer {
|
if err := state.RefreshState(); err != nil {
|
||||||
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
|
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite it
|
dstState := state.State()
|
||||||
if err := state.WriteState(sourceState); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
// If we're not forcing, then perform safety checks
|
||||||
return 1
|
if !flagForce && !dstState.Empty() {
|
||||||
}
|
if !dstState.SameLineage(sourceState) {
|
||||||
if err := state.PersistState(); err != nil {
|
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
return 1
|
||||||
return 1
|
}
|
||||||
}
|
|
||||||
|
age, err := dstState.CompareAges(sourceState)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if age == terraform.StateAgeReceiverNewer {
|
||||||
|
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite it
|
||||||
|
if err := state.WriteState(sourceState); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := state.PersistState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func TestStatePush_replaceMatchStdin(t *testing.T) {
|
||||||
|
|
||||||
// Setup the replacement to come from stdin
|
// Setup the replacement to come from stdin
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := terraform.WriteState(expected, &buf); err != nil {
|
if err := writeStateForTesting(expected, &buf); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
defer testStdinPipe(t, &buf)()
|
defer testStdinPipe(t, &buf)()
|
||||||
|
@ -200,7 +200,7 @@ func TestStatePush_forceRemoteState(t *testing.T) {
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
defer inmem.Reset()
|
defer inmem.Reset()
|
||||||
|
|
||||||
s := terraform.NewState()
|
s := states.NewState()
|
||||||
statePath := testStateFile(t, s)
|
statePath := testStateFile(t, s)
|
||||||
|
|
||||||
// init the backend
|
// init the backend
|
||||||
|
@ -223,11 +223,11 @@ func TestStatePush_forceRemoteState(t *testing.T) {
|
||||||
|
|
||||||
// put a dummy state in place, so we have something to force
|
// put a dummy state in place, so we have something to force
|
||||||
b := backend.TestBackendConfig(t, inmem.New(), nil)
|
b := backend.TestBackendConfig(t, inmem.New(), nil)
|
||||||
sMgr, err := b.State("test")
|
sMgr, err := b.StateMgr("test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := sMgr.WriteState(terraform.NewState()); err != nil {
|
if err := sMgr.WriteState(states.NewState()); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := sMgr.PersistState(); err != nil {
|
if err := sMgr.PersistState(); err != nil {
|
||||||
|
|
|
@ -47,10 +47,15 @@ func (c *StateRmCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateReal.Remove(args...); err != nil {
|
c.Ui.Error("state rm not yet updated for new state types")
|
||||||
c.Ui.Error(fmt.Sprintf(errStateRm, err))
|
return 1
|
||||||
return 1
|
|
||||||
}
|
/*
|
||||||
|
if err := stateReal.Remove(args...); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateRm, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf("%d items removed.", len(args)))
|
c.Ui.Output(fmt.Sprintf("%d items removed.", len(args)))
|
||||||
|
|
||||||
|
|
|
@ -6,43 +6,41 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateRm(t *testing.T) {
|
func TestStateRm(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.bar": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -76,37 +74,32 @@ func TestStateRm(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateRmNoArgs(t *testing.T) {
|
func TestStateRmNoArgs(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.bar": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -138,37 +131,32 @@ func TestStateRm_backupExplicit(t *testing.T) {
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
backupPath := filepath.Join(td, "backup")
|
backupPath := filepath.Join(td, "backup")
|
||||||
|
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"test_instance.bar": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
|
|
@ -2,12 +2,9 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/ryanuber/columnize"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateShowCommand is a Command implementation that shows a single resource.
|
// StateShowCommand is a Command implementation that shows a single resource.
|
||||||
|
@ -38,7 +35,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -54,50 +51,55 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := &terraform.StateFilter{State: stateReal}
|
c.Ui.Error("state show not yet updated for new state types")
|
||||||
results, err := filter.Filter(args...)
|
return 1
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) == 0 {
|
/*
|
||||||
return 0
|
filter := &terraform.StateFilter{State: stateReal}
|
||||||
}
|
results, err := filter.Filter(args...)
|
||||||
|
if err != nil {
|
||||||
instance, err := c.filterInstance(results)
|
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
||||||
if err != nil {
|
return 1
|
||||||
c.Ui.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if instance == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
is := instance.Value.(*terraform.InstanceState)
|
|
||||||
|
|
||||||
// Sort the keys
|
|
||||||
var keys []string
|
|
||||||
for k, _ := range is.Attributes {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
// Build the output
|
|
||||||
var output []string
|
|
||||||
output = append(output, fmt.Sprintf("id | %s", is.ID))
|
|
||||||
for _, k := range keys {
|
|
||||||
if k != "id" {
|
|
||||||
output = append(output, fmt.Sprintf("%s | %s", k, is.Attributes[k]))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Output
|
if len(results) == 0 {
|
||||||
config := columnize.DefaultConfig()
|
return 0
|
||||||
config.Glue = " = "
|
}
|
||||||
c.Ui.Output(columnize.Format(output, config))
|
|
||||||
return 0
|
instance, err := c.filterInstance(results)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if instance == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
is := instance.Value.(*terraform.InstanceState)
|
||||||
|
|
||||||
|
// Sort the keys
|
||||||
|
var keys []string
|
||||||
|
for k, _ := range is.Attributes {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// Build the output
|
||||||
|
var output []string
|
||||||
|
output = append(output, fmt.Sprintf("id | %s", is.ID))
|
||||||
|
for _, k := range keys {
|
||||||
|
if k != "id" {
|
||||||
|
output = append(output, fmt.Sprintf("%s | %s", k, is.Attributes[k]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output
|
||||||
|
config := columnize.DefaultConfig()
|
||||||
|
config.Glue = " = "
|
||||||
|
c.Ui.Output(columnize.Format(output, config))
|
||||||
|
return 0
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StateShowCommand) Help() string {
|
func (c *StateShowCommand) Help() string {
|
||||||
|
|
|
@ -4,31 +4,27 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateShow(t *testing.T) {
|
func TestStateShow(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -57,36 +53,32 @@ func TestStateShow(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateShow_multi(t *testing.T) {
|
func TestStateShow_multi(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo.0": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
|
||||||
Attributes: map[string]string{
|
Status: states.ObjectReady,
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"test_instance.foo.1": &terraform.ResourceState{
|
|
||||||
Type: "test_instance",
|
|
||||||
Primary: &terraform.InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"foo": "value",
|
|
||||||
"bar": "value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "bar",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -127,8 +119,7 @@ func TestStateShow_noState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateShow_emptyState(t *testing.T) {
|
func TestStateShow_emptyState(t *testing.T) {
|
||||||
state := terraform.NewState()
|
state := states.NewState()
|
||||||
|
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
@ -149,34 +140,6 @@ func TestStateShow_emptyState(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateShow_emptyStateWithModule(t *testing.T) {
|
|
||||||
// empty state with empty module
|
|
||||||
state := terraform.NewState()
|
|
||||||
|
|
||||||
mod := &terraform.ModuleState{
|
|
||||||
Path: []string{"root", "mod"},
|
|
||||||
}
|
|
||||||
state.Modules = append(state.Modules, mod)
|
|
||||||
|
|
||||||
statePath := testStateFile(t, state)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
c := &StateShowCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
}
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testStateShowOutput = `
|
const testStateShowOutput = `
|
||||||
id = bar
|
id = bar
|
||||||
bar = value
|
bar = value
|
||||||
|
|
147
command/taint.go
147
command/taint.go
|
@ -3,12 +3,13 @@ package command
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaintCommand is a cli.Command implementation that manually taints
|
// TaintCommand is a cli.Command implementation that manually taints
|
||||||
|
@ -38,6 +39,8 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Require the one argument for the resource to taint
|
// Require the one argument for the resource to taint
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
|
@ -46,34 +49,34 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
if module != "" {
|
||||||
if module == "" {
|
c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".")
|
||||||
module = "root"
|
|
||||||
} else {
|
|
||||||
module = "root." + module
|
|
||||||
}
|
|
||||||
|
|
||||||
rsk, err := terraform.ParseResourceStateKey(name)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rsk.Mode.Taintable() {
|
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
|
||||||
c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name))
|
diags = diags.Append(addrDiags)
|
||||||
|
if addrDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, backendDiags := c.Backend(nil)
|
b, backendDiags := c.Backend(nil)
|
||||||
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
c.showDiagnostics(backendDiags)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -97,63 +100,60 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
s := st.State()
|
s := st.State()
|
||||||
if s.Empty() {
|
if s.Empty() {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(name, module)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"The state is empty. The most common reason for this is that\n" +
|
tfdiags.Error,
|
||||||
"an invalid state file path was given or Terraform has never\n " +
|
"No such resource instance",
|
||||||
"been run for this infrastructure. Infrastructure must exist\n" +
|
"The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
|
||||||
"for it to be tainted."))
|
))
|
||||||
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ModuleState where we will taint. This is provided in a legacy
|
state := s.SyncWrapper()
|
||||||
// string form that doesn't support module instance keys, so we'll shim
|
|
||||||
// it here.
|
// Get the resource and instance we're going to taint
|
||||||
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
|
rs := state.Resource(addr.ContainingResource())
|
||||||
mod := s.ModuleByPath(modPath)
|
is := state.ResourceInstance(addr)
|
||||||
if mod == nil {
|
if is == nil {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(name, module)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"The module %s could not be found. There is nothing to taint.",
|
tfdiags.Error,
|
||||||
module))
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
|
||||||
|
))
|
||||||
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no resources in this module, it is an error
|
obj := is.Current
|
||||||
if len(mod.Resources) == 0 {
|
if obj == nil {
|
||||||
if allowMissing {
|
if len(is.Deposed) != 0 {
|
||||||
return c.allowMissingExit(name, module)
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Don't know why we're here, but we'll produce a generic error message anyway.
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
c.showDiagnostics(diags)
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"The module %s has no resources. There is nothing to taint.",
|
|
||||||
module))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the resource we're looking for
|
obj.Status = states.ObjectTainted
|
||||||
rs, ok := mod.Resources[name]
|
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
|
||||||
if !ok {
|
|
||||||
if allowMissing {
|
|
||||||
return c.allowMissingExit(name, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"The resource %s couldn't be found in the module %s.",
|
|
||||||
name,
|
|
||||||
module))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taint the resource
|
|
||||||
rs.Taint()
|
|
||||||
|
|
||||||
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
|
|
||||||
if err := st.WriteState(s); err != nil {
|
if err := st.WriteState(s); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -163,24 +163,28 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf(
|
c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr))
|
||||||
"The resource %s in the module %s has been marked as tainted!",
|
|
||||||
name, module))
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TaintCommand) Help() string {
|
func (c *TaintCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform taint [options] name
|
Usage: terraform taint [options] <address>
|
||||||
|
|
||||||
Manually mark a resource as tainted, forcing a destroy and recreate
|
Manually mark a resource as tainted, forcing a destroy and recreate
|
||||||
on the next plan/apply.
|
on the next plan/apply.
|
||||||
|
|
||||||
This will not modify your infrastructure. This command changes your
|
This will not modify your infrastructure. This command changes your
|
||||||
state to mark a resource as tainted so that during the next plan or
|
state to mark a resource as tainted so that during the next plan or
|
||||||
apply, that resource will be destroyed and recreated. This command on
|
apply that resource will be destroyed and recreated. This command on
|
||||||
its own will not modify infrastructure. This command can be undone by
|
its own will not modify infrastructure. This command can be undone
|
||||||
reverting the state backup file that is created.
|
using the "terraform untaint" command with the same address.
|
||||||
|
|
||||||
|
The address is in the usual resource address syntax, as shown in
|
||||||
|
the output from other commands, such as:
|
||||||
|
aws_instance.foo
|
||||||
|
aws_instance.bar[1]
|
||||||
|
module.foo.module.bar.aws_instance.baz
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
|
@ -195,10 +199,6 @@ Options:
|
||||||
|
|
||||||
-lock-timeout=0s Duration to retry a state lock.
|
-lock-timeout=0s Duration to retry a state lock.
|
||||||
|
|
||||||
-module=path The module path where the resource lives. By
|
|
||||||
default this will be root. Child modules can be specified
|
|
||||||
by names. Ex. "consul" or "consul.vpc" (nested modules).
|
|
||||||
|
|
||||||
-no-color If specified, output won't contain any color.
|
-no-color If specified, output won't contain any color.
|
||||||
|
|
||||||
-state=path Path to read and save state (unless state-out
|
-state=path Path to read and save state (unless state-out
|
||||||
|
@ -215,10 +215,11 @@ func (c *TaintCommand) Synopsis() string {
|
||||||
return "Manually mark a resource for recreation"
|
return "Manually mark a resource for recreation"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TaintCommand) allowMissingExit(name, module string) int {
|
func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
|
||||||
c.Ui.Output(fmt.Sprintf(
|
c.showDiagnostics(tfdiags.Sourceless(
|
||||||
"The resource %s in the module %s was not found, but\n"+
|
tfdiags.Warning,
|
||||||
"-allow-missing is set, so we're exiting successfully.",
|
"No such resource instance",
|
||||||
name, module))
|
"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
|
||||||
|
))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,26 +5,28 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTaint(t *testing.T) {
|
func TestTaint(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -46,21 +48,20 @@ func TestTaint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaint_lockedState(t *testing.T) {
|
func TestTaint_lockedState(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState("./testdata", statePath)
|
||||||
|
@ -233,21 +234,20 @@ func TestTaint_defaultState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaint_missing(t *testing.T) {
|
func TestTaint_missing(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -267,21 +267,20 @@ func TestTaint_missing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaint_missingAllow(t *testing.T) {
|
func TestTaint_missingAllow(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -344,32 +343,32 @@ func TestTaint_stateOut(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaint_module(t *testing.T) {
|
func TestTaint_module(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
&terraform.ModuleState{
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root", "child"},
|
)
|
||||||
Resources: map[string]*terraform.ResourceState{
|
s.SetResourceInstanceCurrent(
|
||||||
"test_instance.blah": &terraform.ResourceState{
|
addrs.Resource{
|
||||||
Type: "test_instance",
|
Mode: addrs.ManagedResourceMode,
|
||||||
Primary: &terraform.InstanceState{
|
Type: "test_instance",
|
||||||
ID: "blah",
|
Name: "blah",
|
||||||
},
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
|
||||||
},
|
&states.ResourceInstanceObjectSrc{
|
||||||
},
|
AttrsJSON: []byte(`{"id":"blah"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
@ -67,21 +68,13 @@ func (c *UnlockCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocal := false
|
_, isLocal := st.(*statemgr.Filesystem)
|
||||||
switch s := st.(type) {
|
|
||||||
case *state.BackupState:
|
|
||||||
if _, ok := s.Real.(*state.LocalState); ok {
|
|
||||||
isLocal = true
|
|
||||||
}
|
|
||||||
case *state.LocalState:
|
|
||||||
isLocal = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
// Forcing this doesn't do anything, but doesn't break anything either,
|
// Forcing this doesn't do anything, but doesn't break anything either,
|
||||||
|
|
|
@ -25,7 +25,7 @@ func TestUnlock(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
err = terraform.WriteState(testState(), f)
|
err = terraform.WriteState(terraform.NewState(), f)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
|
|
@ -3,9 +3,12 @@ package command
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +40,8 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Require the one argument for the resource to untaint
|
// Require the one argument for the resource to untaint
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
|
@ -45,23 +50,29 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
if module != "" {
|
||||||
if module == "" {
|
c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".")
|
||||||
module = "root"
|
return 1
|
||||||
} else {
|
}
|
||||||
module = "root." + module
|
|
||||||
|
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
|
||||||
|
diags = diags.Append(addrDiags)
|
||||||
|
if addrDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(diags)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, backendDiags := c.Backend(nil)
|
b, backendDiags := c.Backend(nil)
|
||||||
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
c.showDiagnostics(backendDiags)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
workspace := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.StateMgr(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -85,63 +96,69 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
s := st.State()
|
s := st.State()
|
||||||
if s.Empty() {
|
if s.Empty() {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(name, module)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"The state is empty. The most common reason for this is that\n" +
|
tfdiags.Error,
|
||||||
"an invalid state file path was given or Terraform has never\n " +
|
"No such resource instance",
|
||||||
"been run for this infrastructure. Infrastructure must exist\n" +
|
"The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
|
||||||
"for it to be untainted."))
|
))
|
||||||
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the ModuleState where we will untaint. This is provided in a legacy
|
state := s.SyncWrapper()
|
||||||
// string form that doesn't support module instance keys, so we'll shim
|
|
||||||
// it here.
|
// Get the resource and instance we're going to taint
|
||||||
modPath := addrs.Module(strings.Split(module, ".")).UnkeyedInstanceShim()
|
rs := state.Resource(addr.ContainingResource())
|
||||||
mod := s.ModuleByPath(modPath)
|
is := state.ResourceInstance(addr)
|
||||||
if mod == nil {
|
if is == nil {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(name, module)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"The module %s could not be found. There is nothing to untaint.",
|
tfdiags.Error,
|
||||||
module))
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
|
||||||
|
))
|
||||||
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no resources in this module, it is an error
|
obj := is.Current
|
||||||
if len(mod.Resources) == 0 {
|
if obj == nil {
|
||||||
if allowMissing {
|
if len(is.Deposed) != 0 {
|
||||||
return c.allowMissingExit(name, module)
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Don't know why we're here, but we'll produce a generic error message anyway.
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"No such resource instance",
|
||||||
|
fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
c.showDiagnostics(diags)
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"The module %s has no resources. There is nothing to untaint.",
|
|
||||||
module))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the resource we're looking for
|
if obj.Status != states.ObjectTainted {
|
||||||
rs, ok := mod.Resources[name]
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
if !ok {
|
tfdiags.Error,
|
||||||
if allowMissing {
|
"Resource instance is not tainted",
|
||||||
return c.allowMissingExit(name, module)
|
fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr),
|
||||||
}
|
))
|
||||||
|
c.showDiagnostics(diags)
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"The resource %s couldn't be found in the module %s.",
|
|
||||||
name,
|
|
||||||
module))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
obj.Status = states.ObjectReady
|
||||||
|
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
|
||||||
|
|
||||||
// Untaint the resource
|
|
||||||
rs.Untaint()
|
|
||||||
|
|
||||||
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
|
|
||||||
if err := st.WriteState(s); err != nil {
|
if err := st.WriteState(s); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -151,9 +168,7 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Output(fmt.Sprintf(
|
c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr))
|
||||||
"The resource %s in the module %s has been successfully untainted!",
|
|
||||||
name, module))
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,10 +218,11 @@ func (c *UntaintCommand) Synopsis() string {
|
||||||
return "Manually unmark a resource as tainted"
|
return "Manually unmark a resource as tainted"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UntaintCommand) allowMissingExit(name, module string) int {
|
func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
|
||||||
c.Ui.Output(fmt.Sprintf(
|
c.showDiagnostics(tfdiags.Sourceless(
|
||||||
"The resource %s in the module %s was not found, but\n"+
|
tfdiags.Warning,
|
||||||
"-allow-missing is set, so we're exiting successfully.",
|
"No such resource instance",
|
||||||
name, module))
|
"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
|
||||||
|
))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,27 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUntaint(t *testing.T) {
|
func TestUntaint(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
Tainted: true,
|
Status: states.ObjectTainted,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -51,22 +51,20 @@ test_instance.foo:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUntaint_lockedState(t *testing.T) {
|
func TestUntaint_lockedState(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
Tainted: true,
|
Status: states.ObjectTainted,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState("./testdata", statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -257,22 +255,20 @@ test_instance.foo:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUntaint_missing(t *testing.T) {
|
func TestUntaint_missing(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
Tainted: true,
|
Status: states.ObjectTainted,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -292,22 +288,20 @@ func TestUntaint_missing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUntaint_missingAllow(t *testing.T) {
|
func TestUntaint_missingAllow(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
Tainted: true,
|
Status: states.ObjectTainted,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
@ -377,34 +371,32 @@ test_instance.foo:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUntaint_module(t *testing.T) {
|
func TestUntaint_module(t *testing.T) {
|
||||||
state := &terraform.State{
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
Modules: []*terraform.ModuleState{
|
s.SetResourceInstanceCurrent(
|
||||||
&terraform.ModuleState{
|
addrs.Resource{
|
||||||
Path: []string{"root"},
|
Mode: addrs.ManagedResourceMode,
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Type: "test_instance",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
Name: "foo",
|
||||||
Type: "test_instance",
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: "bar",
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
Tainted: true,
|
Status: states.ObjectTainted,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
&terraform.ModuleState{
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
Path: []string{"root", "child"},
|
)
|
||||||
Resources: map[string]*terraform.ResourceState{
|
s.SetResourceInstanceCurrent(
|
||||||
"test_instance.blah": &terraform.ResourceState{
|
addrs.Resource{
|
||||||
Type: "test_instance",
|
Mode: addrs.ManagedResourceMode,
|
||||||
Primary: &terraform.InstanceState{
|
Type: "test_instance",
|
||||||
ID: "bar",
|
Name: "blah",
|
||||||
Tainted: true,
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
|
||||||
},
|
&states.ResourceInstanceObjectSrc{
|
||||||
},
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
},
|
Status: states.ObjectTainted,
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
|
|
|
@ -7,11 +7,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/backend/local"
|
"github.com/hashicorp/terraform/backend/local"
|
||||||
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||||
"github.com/hashicorp/terraform/helper/copy"
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
@ -227,24 +230,22 @@ func TestWorkspace_createWithState(t *testing.T) {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a non-empty state
|
originalState := states.BuildState(func(s *states.SyncState) {
|
||||||
originalState := &terraform.State{
|
s.SetResourceInstanceCurrent(
|
||||||
Modules: []*terraform.ModuleState{
|
addrs.Resource{
|
||||||
&terraform.ModuleState{
|
Mode: addrs.ManagedResourceMode,
|
||||||
Path: []string{"root"},
|
Type: "test_instance",
|
||||||
Resources: map[string]*terraform.ResourceState{
|
Name: "foo",
|
||||||
"test_instance.foo": &terraform.ResourceState{
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
Type: "test_instance",
|
&states.ResourceInstanceObjectSrc{
|
||||||
Primary: &terraform.InstanceState{
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||||
ID: "bar",
|
Status: states.ObjectReady,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
|
|
||||||
err := (&state.LocalState{Path: "test.tfstate"}).WriteState(originalState)
|
err := statemgr.NewFilesystem("test.tfstate").WriteState(originalState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -268,14 +269,13 @@ func TestWorkspace_createWithState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, inmem.New(), nil)
|
b := backend.TestBackendConfig(t, inmem.New(), nil)
|
||||||
sMgr, err := b.State(workspace)
|
sMgr, err := b.StateMgr(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := sMgr.State()
|
newState := sMgr.State()
|
||||||
|
|
||||||
originalState.Version = newState.Version // the round-trip through the state manager implicitly populates version
|
|
||||||
if !originalState.Equal(newState) {
|
if !originalState.Equal(newState) {
|
||||||
t.Fatalf("states not equal\norig: %s\nnew: %s", originalState, newState)
|
t.Fatalf("states not equal\norig: %s\nnew: %s", originalState, newState)
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
@ -94,7 +94,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need the actual state to see if it's empty
|
// we need the actual state to see if it's empty
|
||||||
sMgr, err := b.State(delEnv)
|
sMgr, err := b.StateMgr(delEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
@ -134,7 +134,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
||||||
// be delegated from the Backend to the State itself.
|
// be delegated from the Backend to the State itself.
|
||||||
stateLocker.Unlock(nil)
|
stateLocker.Unlock(nil)
|
||||||
|
|
||||||
err = b.DeleteState(delEnv)
|
err = b.DeleteWorkspace(delEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -53,7 +53,7 @@ func (c *WorkspaceListCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
@ -79,7 +79,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -91,7 +91,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = b.State(newEnv)
|
_, err = b.StateMgr(newEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
@ -112,7 +112,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the new Backend state
|
// load the new Backend state
|
||||||
sMgr, err := b.State(newEnv)
|
sMgr, err := b.StateMgr(newEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
@ -128,20 +128,20 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the existing state file
|
// read the existing state file
|
||||||
stateFile, err := os.Open(statePath)
|
f, err := os.Open(statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := terraform.ReadState(stateFile)
|
stateFile, err := statefile.Read(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the existing state in the new Backend.
|
// save the existing state in the new Backend.
|
||||||
err = sMgr.WriteState(s)
|
err = sMgr.WriteState(stateFile.State)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -75,7 +75,7 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
states, err := b.States()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -2,6 +2,7 @@ package configload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/registry"
|
"github.com/hashicorp/terraform/registry"
|
||||||
|
@ -91,3 +92,35 @@ func (l *Loader) Sources() map[string][]byte {
|
||||||
func (l *Loader) IsConfigDir(path string) bool {
|
func (l *Loader) IsConfigDir(path string) bool {
|
||||||
return l.parser.IsConfigDir(path)
|
return l.parser.IsConfigDir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImportSources writes into the receiver's source code the given source
|
||||||
|
// code buffers.
|
||||||
|
//
|
||||||
|
// This is useful in the situation where an ancillary loader is created for
|
||||||
|
// some reason (e.g. loading config from a plan file) but the cached source
|
||||||
|
// code from that loader must be imported into the "main" loader in order
|
||||||
|
// to return source code snapshots in diagnostic messages.
|
||||||
|
//
|
||||||
|
// loader.ImportSources(otherLoader.Sources())
|
||||||
|
func (l *Loader) ImportSources(sources map[string][]byte) {
|
||||||
|
p := l.Parser()
|
||||||
|
for name, src := range sources {
|
||||||
|
p.ForceFileSource(name, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportSourcesFromSnapshot writes into the receiver's source code the
|
||||||
|
// source files from the given snapshot.
|
||||||
|
//
|
||||||
|
// This is similar to ImportSources but knows how to unpack and flatten a
|
||||||
|
// snapshot data structure to get the corresponding flat source file map.
|
||||||
|
func (l *Loader) ImportSourcesFromSnapshot(snap *Snapshot) {
|
||||||
|
p := l.Parser()
|
||||||
|
for _, m := range snap.Modules {
|
||||||
|
baseDir := m.Dir
|
||||||
|
for fn, src := range m.Files {
|
||||||
|
fullPath := filepath.Join(baseDir, fn)
|
||||||
|
p.ForceFileSource(fullPath, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,16 +14,16 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/logutils"
|
"github.com/hashicorp/logutils"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -674,17 +674,24 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name := fmt.Sprintf("%s.foo", r.Type)
|
addr := addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: r.Type,
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey)
|
||||||
|
absAddr := addr.Absolute(addrs.RootModuleInstance)
|
||||||
|
|
||||||
// Build the state. The state is just the resource with an ID. There
|
// Build the state. The state is just the resource with an ID. There
|
||||||
// are no attributes. We only set what is needed to perform a refresh.
|
// are no attributes. We only set what is needed to perform a refresh.
|
||||||
state := terraform.NewState()
|
state := states.NewState()
|
||||||
state.RootModule().Resources[name] = &terraform.ResourceState{
|
state.RootModule().SetResourceInstanceCurrent(
|
||||||
Type: r.Type,
|
addr,
|
||||||
Primary: &terraform.InstanceState{
|
&states.ResourceInstanceObjectSrc{
|
||||||
ID: r.Primary.ID,
|
AttrsFlat: r.Primary.Attributes,
|
||||||
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
}
|
addrs.ProviderConfig{Type: "placeholder"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
)
|
||||||
|
|
||||||
// Create the config module. We use the full config because Refresh
|
// Create the config module. We use the full config because Refresh
|
||||||
// doesn't have access to it and we may need things like provider
|
// doesn't have access to it and we may need things like provider
|
||||||
|
@ -717,14 +724,14 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify attribute equivalence.
|
// Verify attribute equivalence.
|
||||||
actualR := state.RootModule().Resources[name]
|
actualR := state.ResourceInstance(absAddr)
|
||||||
if actualR == nil {
|
if actualR == nil {
|
||||||
return fmt.Errorf("Resource gone!")
|
return fmt.Errorf("Resource gone!")
|
||||||
}
|
}
|
||||||
if actualR.Primary == nil {
|
if actualR.Current == nil {
|
||||||
return fmt.Errorf("Resource has no primary instance")
|
return fmt.Errorf("Resource has no primary instance")
|
||||||
}
|
}
|
||||||
actual := actualR.Primary.Attributes
|
actual := actualR.Current.AttrsFlat
|
||||||
expected := r.Primary.Attributes
|
expected := r.Primary.Attributes
|
||||||
// Remove fields we're ignoring
|
// Remove fields we're ignoring
|
||||||
for _, v := range c.IDRefreshIgnore {
|
for _, v := range c.IDRefreshIgnore {
|
||||||
|
|
|
@ -3,12 +3,7 @@ package resource
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,149 +15,149 @@ func testStepConfig(
|
||||||
return testStep(opts, state, step)
|
return testStep(opts, state, step)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStep(
|
func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) {
|
||||||
opts terraform.ContextOpts,
|
return nil, fmt.Errorf("testStep not yet updated for new state type")
|
||||||
state *terraform.State,
|
/*
|
||||||
step TestStep) (*terraform.State, error) {
|
// Pre-taint any resources that have been defined in Taint, as long as this
|
||||||
// Pre-taint any resources that have been defined in Taint, as long as this
|
// is not a destroy step.
|
||||||
// is not a destroy step.
|
if !step.Destroy {
|
||||||
if !step.Destroy {
|
if err := testStepTaint(state, step); err != nil {
|
||||||
if err := testStepTaint(state, step); err != nil {
|
return state, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := testConfig(opts, step)
|
||||||
|
if err != nil {
|
||||||
return state, err
|
return state, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := testConfig(opts, step)
|
var stepDiags tfdiags.Diagnostics
|
||||||
if err != nil {
|
|
||||||
return state, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var stepDiags tfdiags.Diagnostics
|
// Build the context
|
||||||
|
opts.Config = cfg
|
||||||
// Build the context
|
opts.State = state
|
||||||
opts.Config = cfg
|
opts.Destroy = step.Destroy
|
||||||
opts.State = state
|
ctx, stepDiags := terraform.NewContext(&opts)
|
||||||
opts.Destroy = step.Destroy
|
|
||||||
ctx, stepDiags := terraform.NewContext(&opts)
|
|
||||||
if stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
|
|
||||||
}
|
|
||||||
if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
|
|
||||||
if stepDiags.HasErrors() {
|
if stepDiags.HasErrors() {
|
||||||
return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
|
return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
|
||||||
}
|
}
|
||||||
|
if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
|
||||||
log.Printf("[WARN] Config warnings:\n%s", stepDiags)
|
if stepDiags.HasErrors() {
|
||||||
}
|
return nil, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
|
||||||
|
|
||||||
// Refresh!
|
|
||||||
state, stepDiags = ctx.Refresh()
|
|
||||||
if stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error refreshing: %s", stepDiags.Err())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this step is a PlanOnly step, skip over this first Plan and subsequent
|
|
||||||
// Apply, and use the follow up Plan that checks for perpetual diffs
|
|
||||||
if !step.PlanOnly {
|
|
||||||
// Plan!
|
|
||||||
if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error planning: %s", stepDiags.Err())
|
|
||||||
} else {
|
|
||||||
log.Printf("[WARN] Test: Step plan: %s", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to keep a copy of the state prior to destroying
|
|
||||||
// such that destroy steps can verify their behaviour in the check
|
|
||||||
// function
|
|
||||||
stateBeforeApplication := state.DeepCopy()
|
|
||||||
|
|
||||||
// Apply the diff, creating real resources.
|
|
||||||
state, stepDiags = ctx.Apply()
|
|
||||||
if stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error applying: %s", stepDiags.Err())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run any configured checks
|
|
||||||
if step.Check != nil {
|
|
||||||
if step.Destroy {
|
|
||||||
if err := step.Check(stateBeforeApplication); err != nil {
|
|
||||||
return state, fmt.Errorf("Check failed: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := step.Check(state); err != nil {
|
|
||||||
return state, fmt.Errorf("Check failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, verify that Plan is now empty and we don't have a perpetual diff issue
|
log.Printf("[WARN] Config warnings:\n%s", stepDiags)
|
||||||
// We do this with TWO plans. One without a refresh.
|
|
||||||
var p *terraform.Plan
|
|
||||||
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error on follow-up plan: %s", stepDiags.Err())
|
|
||||||
}
|
|
||||||
if p.Diff != nil && !p.Diff.Empty() {
|
|
||||||
if step.ExpectNonEmptyPlan {
|
|
||||||
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
|
||||||
} else {
|
|
||||||
return state, fmt.Errorf(
|
|
||||||
"After applying this step, the plan was not empty:\n\n%s", p)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// And another after a Refresh.
|
// Refresh!
|
||||||
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
|
|
||||||
state, stepDiags = ctx.Refresh()
|
state, stepDiags = ctx.Refresh()
|
||||||
if stepDiags.HasErrors() {
|
if stepDiags.HasErrors() {
|
||||||
return state, fmt.Errorf("Error on follow-up refresh: %s", stepDiags.Err())
|
return state, fmt.Errorf("Error refreshing: %s", stepDiags.Err())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
|
|
||||||
return state, fmt.Errorf("Error on second follow-up plan: %s", stepDiags.Err())
|
|
||||||
}
|
|
||||||
empty := p.Diff == nil || p.Diff.Empty()
|
|
||||||
|
|
||||||
// Data resources are tricky because they legitimately get instantiated
|
// If this step is a PlanOnly step, skip over this first Plan and subsequent
|
||||||
// during refresh so that they will be already populated during the
|
// Apply, and use the follow up Plan that checks for perpetual diffs
|
||||||
// plan walk. Because of this, if we have any data resources in the
|
if !step.PlanOnly {
|
||||||
// config we'll end up wanting to destroy them again here. This is
|
// Plan!
|
||||||
// acceptable and expected, and we'll treat it as "empty" for the
|
if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
|
||||||
// sake of this testing.
|
return state, fmt.Errorf("Error planning: %s", stepDiags.Err())
|
||||||
if step.Destroy {
|
} else {
|
||||||
empty = true
|
log.Printf("[WARN] Test: Step plan: %s", p)
|
||||||
|
}
|
||||||
|
|
||||||
for _, moduleDiff := range p.Diff.Modules {
|
// We need to keep a copy of the state prior to destroying
|
||||||
for k, instanceDiff := range moduleDiff.Resources {
|
// such that destroy steps can verify their behaviour in the check
|
||||||
if !strings.HasPrefix(k, "data.") {
|
// function
|
||||||
empty = false
|
stateBeforeApplication := state.DeepCopy()
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !instanceDiff.Destroy {
|
// Apply the diff, creating real resources.
|
||||||
empty = false
|
state, stepDiags = ctx.Apply()
|
||||||
|
if stepDiags.HasErrors() {
|
||||||
|
return state, fmt.Errorf("Error applying: %s", stepDiags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run any configured checks
|
||||||
|
if step.Check != nil {
|
||||||
|
if step.Destroy {
|
||||||
|
if err := step.Check(stateBeforeApplication); err != nil {
|
||||||
|
return state, fmt.Errorf("Check failed: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := step.Check(state); err != nil {
|
||||||
|
return state, fmt.Errorf("Check failed: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !empty {
|
// Now, verify that Plan is now empty and we don't have a perpetual diff issue
|
||||||
if step.ExpectNonEmptyPlan {
|
// We do this with TWO plans. One without a refresh.
|
||||||
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
var p *terraform.Plan
|
||||||
} else {
|
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
|
||||||
return state, fmt.Errorf(
|
return state, fmt.Errorf("Error on follow-up plan: %s", stepDiags.Err())
|
||||||
"After applying this step and refreshing, "+
|
}
|
||||||
"the plan was not empty:\n\n%s", p)
|
if p.Diff != nil && !p.Diff.Empty() {
|
||||||
|
if step.ExpectNonEmptyPlan {
|
||||||
|
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
||||||
|
} else {
|
||||||
|
return state, fmt.Errorf(
|
||||||
|
"After applying this step, the plan was not empty:\n\n%s", p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Made it here, but expected a non-empty plan, fail!
|
// And another after a Refresh.
|
||||||
if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) {
|
if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
|
||||||
return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
|
state, stepDiags = ctx.Refresh()
|
||||||
}
|
if stepDiags.HasErrors() {
|
||||||
|
return state, fmt.Errorf("Error on follow-up refresh: %s", stepDiags.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
|
||||||
|
return state, fmt.Errorf("Error on second follow-up plan: %s", stepDiags.Err())
|
||||||
|
}
|
||||||
|
empty := p.Diff == nil || p.Diff.Empty()
|
||||||
|
|
||||||
// Made it here? Good job test step!
|
// Data resources are tricky because they legitimately get instantiated
|
||||||
return state, nil
|
// during refresh so that they will be already populated during the
|
||||||
|
// plan walk. Because of this, if we have any data resources in the
|
||||||
|
// config we'll end up wanting to destroy them again here. This is
|
||||||
|
// acceptable and expected, and we'll treat it as "empty" for the
|
||||||
|
// sake of this testing.
|
||||||
|
if step.Destroy {
|
||||||
|
empty = true
|
||||||
|
|
||||||
|
for _, moduleDiff := range p.Diff.Modules {
|
||||||
|
for k, instanceDiff := range moduleDiff.Resources {
|
||||||
|
if !strings.HasPrefix(k, "data.") {
|
||||||
|
empty = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !instanceDiff.Destroy {
|
||||||
|
empty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !empty {
|
||||||
|
if step.ExpectNonEmptyPlan {
|
||||||
|
log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p)
|
||||||
|
} else {
|
||||||
|
return state, fmt.Errorf(
|
||||||
|
"After applying this step and refreshing, "+
|
||||||
|
"the plan was not empty:\n\n%s", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made it here, but expected a non-empty plan, fail!
|
||||||
|
if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) {
|
||||||
|
return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made it here? Good job test step!
|
||||||
|
return state, nil
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStepTaint(state *terraform.State, step TestStep) error {
|
func testStepTaint(state *terraform.State, step TestStep) error {
|
||||||
|
|
|
@ -3,15 +3,12 @@ package resource
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,7 +49,7 @@ func testStepImportState(
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Config = cfg
|
opts.Config = cfg
|
||||||
opts.State = terraform.NewState()
|
opts.State = states.NewState()
|
||||||
ctx, stepDiags := terraform.NewContext(&opts)
|
ctx, stepDiags := terraform.NewContext(&opts)
|
||||||
if stepDiags.HasErrors() {
|
if stepDiags.HasErrors() {
|
||||||
return state, stepDiags.Err()
|
return state, stepDiags.Err()
|
||||||
|
@ -89,77 +86,84 @@ func testStepImportState(
|
||||||
|
|
||||||
// Go through the new state and verify
|
// Go through the new state and verify
|
||||||
if step.ImportStateCheck != nil {
|
if step.ImportStateCheck != nil {
|
||||||
var states []*terraform.InstanceState
|
var states []*states.ResourceInstanceObjectSrc
|
||||||
for _, r := range newState.RootModule().Resources {
|
for _, r := range newState.RootModule().Resources {
|
||||||
if r.Primary != nil {
|
for _, i := range r.Instances {
|
||||||
states = append(states, r.Primary)
|
if i.Current != nil {
|
||||||
|
states = append(states, i.Current)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := step.ImportStateCheck(states); err != nil {
|
// TODO: update for new state types
|
||||||
|
return nil, fmt.Errorf("ImportStateCheck call in testStepImportState not yet updated for new state types")
|
||||||
|
/*if err := step.ImportStateCheck(states); err != nil {
|
||||||
return state, err
|
return state, err
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that all the states match
|
// Verify that all the states match
|
||||||
if step.ImportStateVerify {
|
if step.ImportStateVerify {
|
||||||
new := newState.RootModule().Resources
|
return nil, fmt.Errorf("testStepImportStep ImportStateVerify not yet updated for new state types")
|
||||||
old := state.RootModule().Resources
|
/*
|
||||||
for _, r := range new {
|
new := newState.RootModule().Resources
|
||||||
// Find the existing resource
|
old := state.RootModule().Resources
|
||||||
var oldR *terraform.ResourceState
|
for _, r := range new {
|
||||||
for _, r2 := range old {
|
// Find the existing resource
|
||||||
if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type {
|
var oldR *terraform.ResourceState
|
||||||
oldR = r2
|
for _, r2 := range old {
|
||||||
break
|
if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type {
|
||||||
}
|
oldR = r2
|
||||||
}
|
break
|
||||||
if oldR == nil {
|
|
||||||
return state, fmt.Errorf(
|
|
||||||
"Failed state verification, resource with ID %s not found",
|
|
||||||
r.Primary.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare their attributes
|
|
||||||
actual := make(map[string]string)
|
|
||||||
for k, v := range r.Primary.Attributes {
|
|
||||||
actual[k] = v
|
|
||||||
}
|
|
||||||
expected := make(map[string]string)
|
|
||||||
for k, v := range oldR.Primary.Attributes {
|
|
||||||
expected[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove fields we're ignoring
|
|
||||||
for _, v := range step.ImportStateVerifyIgnore {
|
|
||||||
for k, _ := range actual {
|
|
||||||
if strings.HasPrefix(k, v) {
|
|
||||||
delete(actual, k)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, _ := range expected {
|
if oldR == nil {
|
||||||
if strings.HasPrefix(k, v) {
|
return state, fmt.Errorf(
|
||||||
delete(expected, k)
|
"Failed state verification, resource with ID %s not found",
|
||||||
}
|
r.Primary.ID)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
// Compare their attributes
|
||||||
// Determine only the different attributes
|
actual := make(map[string]string)
|
||||||
for k, v := range expected {
|
for k, v := range r.Primary.Attributes {
|
||||||
if av, ok := actual[k]; ok && v == av {
|
actual[k] = v
|
||||||
delete(expected, k)
|
}
|
||||||
delete(actual, k)
|
expected := make(map[string]string)
|
||||||
|
for k, v := range oldR.Primary.Attributes {
|
||||||
|
expected[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove fields we're ignoring
|
||||||
|
for _, v := range step.ImportStateVerifyIgnore {
|
||||||
|
for k, _ := range actual {
|
||||||
|
if strings.HasPrefix(k, v) {
|
||||||
|
delete(actual, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, _ := range expected {
|
||||||
|
if strings.HasPrefix(k, v) {
|
||||||
|
delete(expected, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spewConf := spew.NewDefaultConfig()
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
spewConf.SortKeys = true
|
// Determine only the different attributes
|
||||||
return state, fmt.Errorf(
|
for k, v := range expected {
|
||||||
"ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
|
if av, ok := actual[k]; ok && v == av {
|
||||||
"\n\n%s\n\n%s",
|
delete(expected, k)
|
||||||
spewConf.Sdump(actual), spewConf.Sdump(expected))
|
delete(actual, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spewConf := spew.NewDefaultConfig()
|
||||||
|
spewConf.SortKeys = true
|
||||||
|
return state, fmt.Errorf(
|
||||||
|
"ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
|
||||||
|
"\n\n%s\n\n%s",
|
||||||
|
spewConf.Sdump(actual), spewConf.Sdump(expected))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the old state (non-imported) so we don't change anything.
|
// Return the old state (non-imported) so we don't change anything.
|
||||||
|
|
4
main.go
4
main.go
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/command/format"
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
"github.com/hashicorp/terraform/svchost/disco"
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
@ -113,9 +112,6 @@ func init() {
|
||||||
func wrappedMain() int {
|
func wrappedMain() int {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// We always need to close the DebugInfo before we exit.
|
|
||||||
defer terraform.CloseDebugInfo()
|
|
||||||
|
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[INFO] Terraform version: %s %s %s",
|
"[INFO] Terraform version: %s %s %s",
|
||||||
|
|
|
@ -23,6 +23,38 @@ func NewChanges() *Changes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Changes) Empty() bool {
|
||||||
|
return (len(c.Resources) + len(c.RootOutputs)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceInstance returns the planned change for the current object of the
|
||||||
|
// resource instance of the given address, if any. Returns nil if no change is
|
||||||
|
// planned.
|
||||||
|
func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc {
|
||||||
|
addrStr := addr.String()
|
||||||
|
for _, rc := range c.Resources {
|
||||||
|
if rc.Addr.String() == addrStr && rc.DeposedKey == states.NotDeposed {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceInstanceDeposed returns the plan change of a deposed object of
|
||||||
|
// the resource instance of the given address, if any. Returns nil if no change
|
||||||
|
// is planned.
|
||||||
|
func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc {
|
||||||
|
addrStr := addr.String()
|
||||||
|
for _, rc := range c.Resources {
|
||||||
|
if rc.Addr.String() == addrStr && rc.DeposedKey == key {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ResourceInstanceChange describes a change to a particular resource instance
|
// ResourceInstanceChange describes a change to a particular resource instance
|
||||||
// object.
|
// object.
|
||||||
type ResourceInstanceChange struct {
|
type ResourceInstanceChange struct {
|
||||||
|
|
|
@ -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)
|
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"
|
"sort"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Plan is the top-level type representing a planned set of changes.
|
// Plan is the top-level type representing a planned set of changes.
|
||||||
|
@ -44,6 +46,19 @@ type Backend struct {
|
||||||
Workspace string
|
Workspace string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) {
|
||||||
|
dv, err := NewDynamicValue(config, configSchema.ImpliedType())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Backend{
|
||||||
|
Type: typeName,
|
||||||
|
Config: dv,
|
||||||
|
Workspace: workspaceName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ProviderAddrs returns a list of all of the provider configuration addresses
|
// ProviderAddrs returns a list of all of the provider configuration addresses
|
||||||
// referenced throughout the receiving plan.
|
// referenced throughout the receiving plan.
|
||||||
//
|
//
|
||||||
|
|
|
@ -13,8 +13,8 @@ func TestProviderAddrs(t *testing.T) {
|
||||||
plan := &Plan{
|
plan := &Plan{
|
||||||
VariableValues: map[string]DynamicValue{},
|
VariableValues: map[string]DynamicValue{},
|
||||||
Changes: &Changes{
|
Changes: &Changes{
|
||||||
RootOutputs: map[string]*OutputChange{},
|
RootOutputs: map[string]*OutputChangeSrc{},
|
||||||
Resources: []*ResourceInstanceChange{
|
Resources: []*ResourceInstanceChangeSrc{
|
||||||
{
|
{
|
||||||
Addr: addrs.Resource{
|
Addr: addrs.Resource{
|
||||||
Mode: addrs.ManagedResourceMode,
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
|
|
@ -7,12 +7,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/states/statefile"
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
)
|
)
|
||||||
|
@ -45,8 +43,8 @@ func TestRoundtrip(t *testing.T) {
|
||||||
// file is tested more fully in tfplan_test.go .
|
// file is tested more fully in tfplan_test.go .
|
||||||
planIn := &plans.Plan{
|
planIn := &plans.Plan{
|
||||||
Changes: &plans.Changes{
|
Changes: &plans.Changes{
|
||||||
Resources: []*plans.ResourceInstanceChange{},
|
Resources: []*plans.ResourceInstanceChangeSrc{},
|
||||||
RootOutputs: map[string]*plans.OutputChange{},
|
RootOutputs: map[string]*plans.OutputChangeSrc{},
|
||||||
},
|
},
|
||||||
ProviderSHA256s: map[string][]byte{},
|
ProviderSHA256s: map[string][]byte{},
|
||||||
VariableValues: map[string]plans.DynamicValue{
|
VariableValues: map[string]plans.DynamicValue{
|
||||||
|
|
|
@ -21,16 +21,16 @@ func TestTFPlanRoundTrip(t *testing.T) {
|
||||||
"foo": mustNewDynamicValueStr("foo value"),
|
"foo": mustNewDynamicValueStr("foo value"),
|
||||||
},
|
},
|
||||||
Changes: &plans.Changes{
|
Changes: &plans.Changes{
|
||||||
RootOutputs: map[string]*plans.OutputChange{
|
RootOutputs: map[string]*plans.OutputChangeSrc{
|
||||||
"bar": {
|
"bar": {
|
||||||
Change: plans.Change{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: plans.Create,
|
Action: plans.Create,
|
||||||
After: mustNewDynamicValueStr("bar value"),
|
After: mustNewDynamicValueStr("bar value"),
|
||||||
},
|
},
|
||||||
Sensitive: false,
|
Sensitive: false,
|
||||||
},
|
},
|
||||||
"baz": {
|
"baz": {
|
||||||
Change: plans.Change{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: plans.NoOp,
|
Action: plans.NoOp,
|
||||||
Before: mustNewDynamicValueStr("baz value"),
|
Before: mustNewDynamicValueStr("baz value"),
|
||||||
After: mustNewDynamicValueStr("baz value"),
|
After: mustNewDynamicValueStr("baz value"),
|
||||||
|
@ -38,7 +38,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
|
||||||
Sensitive: false,
|
Sensitive: false,
|
||||||
},
|
},
|
||||||
"secret": {
|
"secret": {
|
||||||
Change: plans.Change{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: plans.Update,
|
Action: plans.Update,
|
||||||
Before: mustNewDynamicValueStr("old secret value"),
|
Before: mustNewDynamicValueStr("old secret value"),
|
||||||
After: mustNewDynamicValueStr("new secret value"),
|
After: mustNewDynamicValueStr("new secret value"),
|
||||||
|
@ -46,7 +46,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Resources: []*plans.ResourceInstanceChange{
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||||
{
|
{
|
||||||
Addr: addrs.Resource{
|
Addr: addrs.Resource{
|
||||||
Mode: addrs.ManagedResourceMode,
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
@ -56,7 +56,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
|
||||||
ProviderAddr: addrs.ProviderConfig{
|
ProviderAddr: addrs.ProviderConfig{
|
||||||
Type: "test",
|
Type: "test",
|
||||||
}.Absolute(addrs.RootModuleInstance),
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
Change: plans.Change{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: plans.Replace,
|
Action: plans.Replace,
|
||||||
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
|
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.StringVal("foo-bar-baz"),
|
"id": cty.StringVal("foo-bar-baz"),
|
||||||
|
@ -76,7 +76,7 @@ func TestTFPlanRoundTrip(t *testing.T) {
|
||||||
ProviderAddr: addrs.ProviderConfig{
|
ProviderAddr: addrs.ProviderConfig{
|
||||||
Type: "test",
|
Type: "test",
|
||||||
}.Absolute(addrs.RootModuleInstance),
|
}.Absolute(addrs.RootModuleInstance),
|
||||||
Change: plans.Change{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: plans.Delete,
|
Action: plans.Delete,
|
||||||
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
|
Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.StringVal("bar-baz-foo"),
|
"id": cty.StringVal("bar-baz-foo"),
|
||||||
|
|
|
@ -81,7 +81,7 @@ type GetSchemaResponse struct {
|
||||||
// Schema pairs a provider or resource schema with that schema's version.
|
// Schema pairs a provider or resource schema with that schema's version.
|
||||||
// This is used to be able to upgrade the schema in UpgradeResourceState.
|
// This is used to be able to upgrade the schema in UpgradeResourceState.
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
Version int
|
Version uint64
|
||||||
Block *configschema.Block
|
Block *configschema.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ package state
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackupState wraps a State that backs up the state on the first time that
|
// BackupState wraps a State that backs up the state on the first time that
|
||||||
|
@ -18,7 +19,7 @@ type BackupState struct {
|
||||||
done bool
|
done bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BackupState) State() *terraform.State {
|
func (s *BackupState) State() *states.State {
|
||||||
return s.Real.State()
|
return s.Real.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func (s *BackupState) RefreshState() error {
|
||||||
return s.Real.RefreshState()
|
return s.Real.RefreshState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BackupState) WriteState(state *terraform.State) error {
|
func (s *BackupState) WriteState(state *states.State) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ func (s *BackupState) backup() error {
|
||||||
// purposes, but we don't need a backup or lock if the state is empty, so
|
// purposes, but we don't need a backup or lock if the state is empty, so
|
||||||
// skip this with a nil state.
|
// skip this with a nil state.
|
||||||
if state != nil {
|
if state != nil {
|
||||||
ls := &LocalState{Path: s.Path}
|
ls := statemgr.NewFilesystem(s.Path)
|
||||||
if err := ls.WriteState(state); err != nil {
|
if err := ls.WriteState(state); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue